add offline grade computation & DB table for this
This commit is contained in:
@@ -12,6 +12,8 @@ admin.site.register(UserTestGroup)
|
||||
|
||||
admin.site.register(CourseEnrollment)
|
||||
|
||||
admin.site.register(CourseEnrollmentAllowed)
|
||||
|
||||
admin.site.register(Registration)
|
||||
|
||||
admin.site.register(PendingNameChange)
|
||||
|
||||
@@ -5,8 +5,6 @@ like DISABLE_START_DATES"""
|
||||
import logging
|
||||
import time
|
||||
|
||||
import student.models
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
@@ -15,6 +13,13 @@ from xmodule.modulestore import Location
|
||||
from xmodule.timeparse import parse_time
|
||||
from xmodule.x_module import XModule, XModuleDescriptor
|
||||
|
||||
# student.models imports Role, which imports courseware.access ; use a try, to break the circular import
|
||||
try:
|
||||
from student.models import CourseEnrollmentAllowed
|
||||
except Exception as err:
|
||||
CourseEnrollmentAllowed = None
|
||||
|
||||
|
||||
DEBUG_ACCESS = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -127,8 +132,9 @@ def _has_access_course_desc(user, course, action):
|
||||
return True
|
||||
|
||||
# if user is in CourseEnrollmentAllowed with right course_id then can also enroll
|
||||
if user is not None and student.models.CourseEnrollmentAllowed.objects.filter(email=user.email, course_id=course.id):
|
||||
return True
|
||||
if user is not None and CourseEnrollmentAllowed:
|
||||
if CourseEnrollmentAllowed.objects.filter(email=user.email, course_id=course.id):
|
||||
return True
|
||||
|
||||
# otherwise, need staff access
|
||||
return _has_staff_access_to_descriptor(user, course)
|
||||
|
||||
@@ -7,3 +7,8 @@ from django.contrib import admin
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
admin.site.register(StudentModule)
|
||||
|
||||
admin.site.register(OfflineComputedGrade)
|
||||
|
||||
admin.site.register(OfflineComputedGradeLog)
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'OfflineComputedGrade'
|
||||
db.create_table('courseware_offlinecomputedgrade', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('course_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
|
||||
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, db_index=True, blank=True)),
|
||||
('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)),
|
||||
('gradeset', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('courseware', ['OfflineComputedGrade'])
|
||||
|
||||
# Adding unique constraint on 'OfflineComputedGrade', fields ['user', 'course_id']
|
||||
db.create_unique('courseware_offlinecomputedgrade', ['user_id', 'course_id'])
|
||||
|
||||
# Adding model 'OfflineComputedGradeLog'
|
||||
db.create_table('courseware_offlinecomputedgradelog', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('course_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
|
||||
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, db_index=True, blank=True)),
|
||||
('seconds', self.gf('django.db.models.fields.IntegerField')(default=0)),
|
||||
('nstudents', self.gf('django.db.models.fields.IntegerField')(default=0)),
|
||||
))
|
||||
db.send_create_signal('courseware', ['OfflineComputedGradeLog'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'OfflineComputedGrade', fields ['user', 'course_id']
|
||||
db.delete_unique('courseware_offlinecomputedgrade', ['user_id', 'course_id'])
|
||||
|
||||
# Deleting model 'OfflineComputedGrade'
|
||||
db.delete_table('courseware_offlinecomputedgrade')
|
||||
|
||||
# Deleting model 'OfflineComputedGradeLog'
|
||||
db.delete_table('courseware_offlinecomputedgradelog')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'courseware.offlinecomputedgrade': {
|
||||
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'OfflineComputedGrade'},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'gradeset': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'courseware.offlinecomputedgradelog': {
|
||||
'Meta': {'object_name': 'OfflineComputedGradeLog'},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'nstudents': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'seconds': ('django.db.models.fields.IntegerField', [], {'default': '0'})
|
||||
},
|
||||
'courseware.studentmodule': {
|
||||
'Meta': {'unique_together': "(('student', 'module_state_key', 'course_id'),)", 'object_name': 'StudentModule'},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'done': ('django.db.models.fields.CharField', [], {'default': "'na'", 'max_length': '8', 'db_index': 'True'}),
|
||||
'grade': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'module_state_key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'module_id'", 'db_index': 'True'}),
|
||||
'module_type': ('django.db.models.fields.CharField', [], {'default': "'problem'", 'max_length': '32', 'db_index': 'True'}),
|
||||
'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['courseware']
|
||||
@@ -177,3 +177,40 @@ class StudentModuleCache(object):
|
||||
|
||||
def append(self, student_module):
|
||||
self.cache.append(student_module)
|
||||
|
||||
|
||||
class OfflineComputedGrade(models.Model):
|
||||
"""
|
||||
Table of grades computed offline for a given user and course.
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True)
|
||||
course_id = models.CharField(max_length=255, db_index=True)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
|
||||
updated = models.DateTimeField(auto_now=True, db_index=True)
|
||||
|
||||
gradeset = models.TextField(null=True, blank=True) # grades, stored as JSON
|
||||
|
||||
class Meta:
|
||||
unique_together = (('user', 'course_id'), )
|
||||
|
||||
def __unicode__(self):
|
||||
return "[OfflineComputedGrade] %s: %s (%s) = %s" % (self.user, self.course_id, self.created, self.gradeset)
|
||||
|
||||
|
||||
class OfflineComputedGradeLog(models.Model):
|
||||
"""
|
||||
Log of when offline grades are computed.
|
||||
Use this to be able to show instructor when the last computed grades were done.
|
||||
"""
|
||||
class Meta:
|
||||
ordering = ["-created"]
|
||||
get_latest_by = "created"
|
||||
|
||||
course_id = models.CharField(max_length=255, db_index=True)
|
||||
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
|
||||
seconds = models.IntegerField(default=0) # seconds elapsed for computation
|
||||
nstudents = models.IntegerField(default=0)
|
||||
|
||||
def __unicode__(self):
|
||||
return "[OCGLog] %s: %s" % (self.course_id, self.created)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# django management command: dump grades to csv files
|
||||
# for use by batch processes
|
||||
|
||||
import os, sys, string
|
||||
import datetime
|
||||
import json
|
||||
|
||||
#import student.models
|
||||
from instructor.offline_gradecalc import *
|
||||
from courseware.courses import get_course_by_id
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Compute grades for all students in a course, and store result in DB.\n"
|
||||
help += "Usage: compute_grades course_id_or_dir \n"
|
||||
help += " course_id_or_dir: either course_id or course_dir\n"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
print "args = ", args
|
||||
|
||||
course_id = 'MITx/8.01rq_MW/Classical_Mechanics_Reading_Questions_Fall_2012_MW_Section'
|
||||
|
||||
if len(args)>0:
|
||||
course_id = args[0]
|
||||
|
||||
try:
|
||||
course = get_course_by_id(course_id)
|
||||
except Exception as err:
|
||||
if course_id in modulestore().courses:
|
||||
course = modulestore().courses[course_id]
|
||||
else:
|
||||
print "-----------------------------------------------------------------------------"
|
||||
print "Sorry, cannot find course %s" % course_id
|
||||
print "Please provide a course ID or course data directory name, eg content-mit-801rq"
|
||||
return
|
||||
|
||||
print "-----------------------------------------------------------------------------"
|
||||
print "Computing grades for %s" % (course.id)
|
||||
|
||||
offline_grade_calculation(course.id)
|
||||
|
||||
|
||||
|
||||
|
||||
103
lms/djangoapps/instructor/offline_gradecalc.py
Normal file
103
lms/djangoapps/instructor/offline_gradecalc.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# ======== Offline calculation of grades =============================================================================
|
||||
#
|
||||
# Computing grades of a large number of students can take a long time. These routines allow grades to
|
||||
# be computed offline, by a batch process (eg cronjob).
|
||||
#
|
||||
# The grades are stored in the OfflineComputedGrade table of the courseware model.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
|
||||
import courseware.models
|
||||
|
||||
from collections import namedtuple
|
||||
from json import JSONEncoder
|
||||
from courseware import grades, models
|
||||
from courseware.courses import get_course_by_id
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
|
||||
class MyEncoder(JSONEncoder):
|
||||
|
||||
def _iterencode(self, obj, markers=None):
|
||||
if isinstance(obj, tuple) and hasattr(obj, '_asdict'):
|
||||
gen = self._iterencode_dict(obj._asdict(), markers)
|
||||
else:
|
||||
gen = JSONEncoder._iterencode(self, obj, markers)
|
||||
for chunk in gen:
|
||||
yield chunk
|
||||
|
||||
|
||||
def offline_grade_calculation(course_id):
|
||||
'''
|
||||
Compute grades for all students for a specified course, and save results to the DB.
|
||||
'''
|
||||
|
||||
tstart = time.time()
|
||||
enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).prefetch_related("groups").order_by('username')
|
||||
|
||||
enc = MyEncoder()
|
||||
|
||||
class DummyRequest(object):
|
||||
META = {}
|
||||
def __init__(self):
|
||||
return
|
||||
def get_host(self):
|
||||
return 'edx.mit.edu'
|
||||
def is_secure(self):
|
||||
return False
|
||||
|
||||
request = DummyRequest()
|
||||
|
||||
print "%d enrolled students" % len(enrolled_students)
|
||||
course = get_course_by_id(course_id)
|
||||
|
||||
for student in enrolled_students:
|
||||
gradeset = grades.grade(student, request, course, keep_raw_scores=True)
|
||||
gs = enc.encode(gradeset)
|
||||
ocg, created = models.OfflineComputedGrade.objects.get_or_create(user=student, course_id=course_id)
|
||||
ocg.gradeset = gs
|
||||
ocg.save()
|
||||
print "%s done" % student # print statement used because this is run by a management command
|
||||
|
||||
tend = time.time()
|
||||
dt = tend - tstart
|
||||
|
||||
ocgl = models.OfflineComputedGradeLog(course_id=course_id, seconds=dt, nstudents=len(enrolled_students))
|
||||
ocgl.save()
|
||||
print ocgl
|
||||
print "All Done!"
|
||||
|
||||
|
||||
def offline_grades_available(course_id):
|
||||
'''
|
||||
Returns False if no offline grades available for specified course.
|
||||
Otherwise returns latest log field entry about the available pre-computed grades.
|
||||
'''
|
||||
ocgl = models.OfflineComputedGradeLog.objects.filter(course_id=course_id)
|
||||
if not ocgl:
|
||||
return False
|
||||
return ocgl.latest('created')
|
||||
|
||||
|
||||
def student_grades(student, request, course, keep_raw_scores=False, use_offline=False):
|
||||
'''
|
||||
This is the main interface to get grades. It has the same parameters as grades.grade, as well
|
||||
as use_offline. If use_offline is True then this will look for an offline computed gradeset in the DB.
|
||||
'''
|
||||
|
||||
if not use_offline:
|
||||
return grades.grade(student, request, course, keep_raw_scores=keep_raw_scores)
|
||||
|
||||
try:
|
||||
ocg = models.OfflineComputedGrade.objects.get(user=student, course_id=course.id)
|
||||
except models.OfflineComputedGrade.DoesNotExist:
|
||||
return dict(raw_scores=[], section_breakdown=[],
|
||||
msg='Error: no offline gradeset available for %s, %s' % (student, course.id))
|
||||
|
||||
return json.loads(ocg.gradeset)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundErr
|
||||
from xmodule.modulestore.search import path_to_location
|
||||
import track.views
|
||||
|
||||
|
||||
from .grading import StaffGrading
|
||||
from .offline_gradecalc import student_grades, offline_grades_available
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -103,6 +104,7 @@ def instructor_dashboard(request, course_id):
|
||||
|
||||
# process actions from form POST
|
||||
action = request.POST.get('action', '')
|
||||
use_offline = request.POST.get('use_offline_grades',False)
|
||||
|
||||
if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD']:
|
||||
if 'GIT pull' in action:
|
||||
@@ -134,32 +136,32 @@ def instructor_dashboard(request, course_id):
|
||||
|
||||
if action == 'Dump list of enrolled students' or action=='List enrolled students':
|
||||
log.debug(action)
|
||||
datatable = get_student_grade_summary_data(request, course, course_id, get_grades=False)
|
||||
datatable = get_student_grade_summary_data(request, course, course_id, get_grades=False, use_offline=use_offline)
|
||||
datatable['title'] = 'List of students enrolled in {0}'.format(course_id)
|
||||
track.views.server_track(request, 'list-students', {}, page='idashboard')
|
||||
|
||||
elif 'Dump Grades' in action:
|
||||
log.debug(action)
|
||||
datatable = get_student_grade_summary_data(request, course, course_id, get_grades=True)
|
||||
datatable = get_student_grade_summary_data(request, course, course_id, get_grades=True, use_offline=use_offline)
|
||||
datatable['title'] = 'Summary Grades of students enrolled in {0}'.format(course_id)
|
||||
track.views.server_track(request, 'dump-grades', {}, page='idashboard')
|
||||
|
||||
elif 'Dump all RAW grades' in action:
|
||||
log.debug(action)
|
||||
datatable = get_student_grade_summary_data(request, course, course_id, get_grades=True,
|
||||
get_raw_scores=True)
|
||||
get_raw_scores=True, use_offline=use_offline)
|
||||
datatable['title'] = 'Raw Grades of students enrolled in {0}'.format(course_id)
|
||||
track.views.server_track(request, 'dump-grades-raw', {}, page='idashboard')
|
||||
|
||||
elif 'Download CSV of all student grades' in action:
|
||||
track.views.server_track(request, 'dump-grades-csv', {}, page='idashboard')
|
||||
return return_csv('grades_{0}.csv'.format(course_id),
|
||||
get_student_grade_summary_data(request, course, course_id))
|
||||
get_student_grade_summary_data(request, course, course_id, use_offline=use_offline))
|
||||
|
||||
elif 'Download CSV of all RAW grades' in action:
|
||||
track.views.server_track(request, 'dump-grades-csv-raw', {}, page='idashboard')
|
||||
return return_csv('grades_{0}_raw.csv'.format(course_id),
|
||||
get_student_grade_summary_data(request, course, course_id, get_raw_scores=True))
|
||||
get_student_grade_summary_data(request, course, course_id, get_raw_scores=True, use_offline=use_offline))
|
||||
|
||||
elif 'Download CSV of answer distributions' in action:
|
||||
track.views.server_track(request, 'dump-answer-dist-csv', {}, page='idashboard')
|
||||
@@ -174,7 +176,7 @@ def instructor_dashboard(request, course_id):
|
||||
|
||||
elif action=='List assignments available for this course':
|
||||
log.debug(action)
|
||||
allgrades = get_student_grade_summary_data(request, course, course_id, get_grades=True)
|
||||
allgrades = get_student_grade_summary_data(request, course, course_id, get_grades=True, use_offline=use_offline)
|
||||
|
||||
assignments = [[x] for x in allgrades['assignments']]
|
||||
datatable = {'header': ['Assignment Name']}
|
||||
@@ -184,7 +186,7 @@ def instructor_dashboard(request, course_id):
|
||||
msg += 'assignments=<pre>%s</pre>' % assignments
|
||||
|
||||
elif action=='List enrolled students matching remote gradebook':
|
||||
stud_data = get_student_grade_summary_data(request, course, course_id, get_grades=False)
|
||||
stud_data = get_student_grade_summary_data(request, course, course_id, get_grades=False, use_offline=use_offline)
|
||||
msg2, rg_stud_data = _do_remote_gradebook(request.user, course, 'get-membership')
|
||||
datatable = {'header': ['Student email', 'Match?']}
|
||||
rg_students = [ x['email'] for x in rg_stud_data['retdata'] ]
|
||||
@@ -202,7 +204,7 @@ def instructor_dashboard(request, course_id):
|
||||
if not aname:
|
||||
msg += "<font color='red'>Please enter an assignment name</font>"
|
||||
else:
|
||||
allgrades = get_student_grade_summary_data(request, course, course_id, get_grades=True)
|
||||
allgrades = get_student_grade_summary_data(request, course, course_id, get_grades=True, use_offline=use_offline)
|
||||
if aname not in allgrades['assignments']:
|
||||
msg += "<font color='red'>Invalid assignment name '%s'</font>" % aname
|
||||
else:
|
||||
@@ -401,6 +403,12 @@ def instructor_dashboard(request, course_id):
|
||||
problems = psychoanalyze.problems_with_psychometric_data(course_id)
|
||||
|
||||
|
||||
#----------------------------------------
|
||||
# offline grades?
|
||||
|
||||
if use_offline:
|
||||
msg += "<br/><font color='orange'>Grades from %s</font>" % offline_grades_available(course_id)
|
||||
|
||||
#----------------------------------------
|
||||
# context for rendering
|
||||
|
||||
@@ -416,7 +424,8 @@ def instructor_dashboard(request, course_id):
|
||||
'plots': plots, # psychometrics
|
||||
'course_errors': modulestore().get_item_errors(course.location),
|
||||
'djangopid' : os.getpid(),
|
||||
'mitx_version' : getattr(settings,'MITX_VERSION_STRING','')
|
||||
'mitx_version' : getattr(settings,'MITX_VERSION_STRING',''),
|
||||
'offline_grade_log' : offline_grades_available(course_id),
|
||||
}
|
||||
|
||||
return render_to_response('courseware/instructor_dashboard.html', context)
|
||||
@@ -539,7 +548,7 @@ def _update_forum_role_membership(uname, course, rolename, add_or_remove):
|
||||
return msg
|
||||
|
||||
|
||||
def get_student_grade_summary_data(request, course, course_id, get_grades=True, get_raw_scores=False):
|
||||
def get_student_grade_summary_data(request, course, course_id, get_grades=True, get_raw_scores=False, use_offline=False):
|
||||
'''
|
||||
Return data arrays with student identity and grades for specified course.
|
||||
|
||||
@@ -563,7 +572,7 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
|
||||
assignments = []
|
||||
if get_grades and enrolled_students.count() > 0:
|
||||
# just to construct the header
|
||||
gradeset = grades.grade(enrolled_students[0], request, course, keep_raw_scores=get_raw_scores)
|
||||
gradeset = student_grades(enrolled_students[0], request, course, keep_raw_scores=get_raw_scores, use_offline=use_offline)
|
||||
# log.debug('student {0} gradeset {1}'.format(enrolled_students[0], gradeset))
|
||||
if get_raw_scores:
|
||||
assignments += [score.section for score in gradeset['raw_scores']]
|
||||
@@ -582,20 +591,22 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
|
||||
datarow.append('')
|
||||
|
||||
if get_grades:
|
||||
gradeset = grades.grade(student, request, course, keep_raw_scores=get_raw_scores)
|
||||
gradeset = student_grades(student, request, course, keep_raw_scores=get_raw_scores, use_offline=use_offline)
|
||||
log.debug('student={0}, gradeset={1}'.format(student,gradeset))
|
||||
if get_raw_scores:
|
||||
student_grades = [score.earned for score in gradeset['raw_scores']]
|
||||
# TODO (ichuang) encode Score as dict instead of as list, so score[0] -> score['earned']
|
||||
sgrades = [(getattr(score,'earned','') or score[0]) for score in gradeset['raw_scores']]
|
||||
else:
|
||||
student_grades = [x['percent'] for x in gradeset['section_breakdown']]
|
||||
datarow += student_grades
|
||||
student.grades = student_grades # store in student object
|
||||
sgrades = [x['percent'] for x in gradeset['section_breakdown']]
|
||||
datarow += sgrades
|
||||
student.grades = sgrades # store in student object
|
||||
|
||||
data.append(datarow)
|
||||
datatable['data'] = data
|
||||
return datatable
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Staff grading
|
||||
|
||||
|
||||
|
||||
@@ -616,7 +627,7 @@ def gradebook(request, course_id):
|
||||
student_info = [{'username': student.username,
|
||||
'id': student.id,
|
||||
'email': student.email,
|
||||
'grade_summary': grades.grade(student, request, course),
|
||||
'grade_summary': student_grades(student, request, course),
|
||||
'realname': student.profile.name,
|
||||
}
|
||||
for student in enrolled_students]
|
||||
@@ -639,6 +650,10 @@ def grade_summary(request, course_id):
|
||||
return render_to_response('courseware/grade_summary.html', context)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# enrollment
|
||||
|
||||
|
||||
def _do_enroll_students(course, course_id, students, overload=False):
|
||||
"""Do the actual work of enrolling multiple students, presented as a string
|
||||
of emails separated by commas or returns"""
|
||||
@@ -731,6 +746,9 @@ def enroll_students(request, course_id):
|
||||
'debug': new_students})
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# answer distribution
|
||||
|
||||
def get_answers_distribution(request, course_id):
|
||||
"""
|
||||
Get the distribution of answers for all graded problems in the course.
|
||||
|
||||
@@ -71,6 +71,12 @@ function goto( mode)
|
||||
|
||||
##-----------------------------------------------------------------------------
|
||||
%if modeflag.get('Grades'):
|
||||
|
||||
%if offline_grade_log:
|
||||
<p><font color='orange'>Pre-computed grades ${offline_grade_log} available: Use?
|
||||
<input type='checkbox' name='use_offline_grades' value='yes'></font> </p>
|
||||
%endif
|
||||
|
||||
<p>
|
||||
<a href="${reverse('gradebook', kwargs=dict(course_id=course.id))}">Gradebook</a>
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user