Merge pull request #329 from MITx/MITx/feature/bridger/fast_course_grading
Got profile page working again.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
from fs.errors import ResourceNotFoundError
|
||||
import time
|
||||
import dateutil.parser
|
||||
import logging
|
||||
|
||||
from xmodule.graders import load_grading_policy
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.seq_module import SequenceDescriptor, SequenceModule
|
||||
|
||||
@@ -14,6 +16,9 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
|
||||
def __init__(self, system, definition=None, **kwargs):
|
||||
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
|
||||
|
||||
self._grader = None
|
||||
self._grade_cutoffs = None
|
||||
|
||||
try:
|
||||
self.start = time.strptime(self.metadata["start"], "%Y-%m-%dT%H:%M")
|
||||
@@ -27,7 +32,34 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
|
||||
def has_started(self):
|
||||
return time.gmtime() > self.start
|
||||
|
||||
|
||||
@property
|
||||
def grader(self):
|
||||
self.__load_grading_policy()
|
||||
return self._grader
|
||||
|
||||
@property
|
||||
def grade_cutoffs(self):
|
||||
self.__load_grading_policy()
|
||||
return self._grade_cutoffs
|
||||
|
||||
|
||||
def __load_grading_policy(self):
|
||||
if not self._grader or not self._grade_cutoffs:
|
||||
policy_string = ""
|
||||
|
||||
try:
|
||||
with self.system.resources_fs.open("grading_policy.json") as grading_policy_file:
|
||||
policy_string = grading_policy_file.read()
|
||||
except (IOError, ResourceNotFoundError):
|
||||
log.warning("Unable to load course settings file from grading_policy.json in course " + self.id)
|
||||
|
||||
grading_policy = load_grading_policy(policy_string)
|
||||
|
||||
self._grader = grading_policy['GRADER']
|
||||
self._grade_cutoffs = grading_policy['GRADE_CUTOFFS']
|
||||
|
||||
|
||||
@staticmethod
|
||||
def id_to_location(course_id):
|
||||
'''Convert the given course_id (org/course/name) to a location object.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import abc
|
||||
import json
|
||||
import logging
|
||||
|
||||
from collections import namedtuple
|
||||
@@ -9,6 +10,69 @@ log = logging.getLogger("mitx.courseware")
|
||||
# Section either indicates the name of the problem or the name of the section
|
||||
Score = namedtuple("Score", "earned possible graded section")
|
||||
|
||||
def load_grading_policy(course_policy_string):
|
||||
"""
|
||||
This loads a grading policy from a string (usually read from a file),
|
||||
which can be a JSON object or an empty string.
|
||||
|
||||
The JSON object can have the keys GRADER and GRADE_CUTOFFS. If either is
|
||||
missing, it reverts to the default.
|
||||
"""
|
||||
|
||||
default_policy_string = """
|
||||
{
|
||||
"GRADER" : [
|
||||
{
|
||||
"type" : "Homework",
|
||||
"min_count" : 12,
|
||||
"drop_count" : 2,
|
||||
"short_label" : "HW",
|
||||
"weight" : 0.15
|
||||
},
|
||||
{
|
||||
"type" : "Lab",
|
||||
"min_count" : 12,
|
||||
"drop_count" : 2,
|
||||
"category" : "Labs",
|
||||
"weight" : 0.15
|
||||
},
|
||||
{
|
||||
"type" : "Midterm",
|
||||
"name" : "Midterm Exam",
|
||||
"short_label" : "Midterm",
|
||||
"weight" : 0.3
|
||||
},
|
||||
{
|
||||
"type" : "Final",
|
||||
"name" : "Final Exam",
|
||||
"short_label" : "Final",
|
||||
"weight" : 0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS" : {
|
||||
"A" : 0.87,
|
||||
"B" : 0.7,
|
||||
"C" : 0.6
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# Load the global settings as a dictionary
|
||||
grading_policy = json.loads(default_policy_string)
|
||||
|
||||
# Load the course policies as a dictionary
|
||||
course_policy = {}
|
||||
if course_policy_string:
|
||||
course_policy = json.loads(course_policy_string)
|
||||
|
||||
# Override any global settings with the course settings
|
||||
grading_policy.update(course_policy)
|
||||
|
||||
# Here is where we should parse any configurations, so that we can fail early
|
||||
grading_policy['GRADER'] = grader_from_conf(grading_policy['GRADER'])
|
||||
|
||||
return grading_policy
|
||||
|
||||
|
||||
def aggregate_scores(scores, section_name="summary"):
|
||||
"""
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
"""
|
||||
Course settings module. All settings in the global_settings are
|
||||
first applied, and then any settings in the settings.DATA_DIR/course_settings.json
|
||||
are applied. A setting must be in ALL_CAPS.
|
||||
|
||||
Settings are used by calling
|
||||
|
||||
from courseware.course_settings import course_settings
|
||||
|
||||
Note that courseware.course_settings.course_settings is not a module -- it's an object. So
|
||||
importing individual settings is not possible:
|
||||
|
||||
from courseware.course_settings.course_settings import GRADER # This won't work.
|
||||
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule import graders
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
global_settings_json = """
|
||||
{
|
||||
"GRADER" : [
|
||||
{
|
||||
"type" : "Homework",
|
||||
"min_count" : 12,
|
||||
"drop_count" : 2,
|
||||
"short_label" : "HW",
|
||||
"weight" : 0.15
|
||||
},
|
||||
{
|
||||
"type" : "Lab",
|
||||
"min_count" : 12,
|
||||
"drop_count" : 2,
|
||||
"category" : "Labs",
|
||||
"weight" : 0.15
|
||||
},
|
||||
{
|
||||
"type" : "Midterm",
|
||||
"name" : "Midterm Exam",
|
||||
"short_label" : "Midterm",
|
||||
"weight" : 0.3
|
||||
},
|
||||
{
|
||||
"type" : "Final",
|
||||
"name" : "Final Exam",
|
||||
"short_label" : "Final",
|
||||
"weight" : 0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS" : {
|
||||
"A" : 0.87,
|
||||
"B" : 0.7,
|
||||
"C" : 0.6
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class Settings(object):
|
||||
def __init__(self):
|
||||
|
||||
# Load the global settings as a dictionary
|
||||
global_settings = json.loads(global_settings_json)
|
||||
|
||||
# Load the course settings as a dictionary
|
||||
course_settings = {}
|
||||
try:
|
||||
# TODO: this doesn't work with multicourse
|
||||
with open(settings.DATA_DIR + "/course_settings.json") as course_settings_file:
|
||||
course_settings_string = course_settings_file.read()
|
||||
course_settings = json.loads(course_settings_string)
|
||||
except IOError:
|
||||
log.warning("Unable to load course settings file from " + str(settings.DATA_DIR) + "/course_settings.json")
|
||||
|
||||
# Override any global settings with the course settings
|
||||
global_settings.update(course_settings)
|
||||
|
||||
# Now, set the properties from the course settings on ourselves
|
||||
for setting in global_settings:
|
||||
setting_value = global_settings[setting]
|
||||
setattr(self, setting, setting_value)
|
||||
|
||||
# Here is where we should parse any configurations, so that we can fail early
|
||||
self.GRADER = graders.grader_from_conf(self.GRADER)
|
||||
|
||||
course_settings = Settings()
|
||||
@@ -3,7 +3,6 @@ import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from courseware.course_settings import course_settings
|
||||
from xmodule import graders
|
||||
from xmodule.graders import Score
|
||||
from models import StudentModule
|
||||
@@ -11,7 +10,7 @@ from models import StudentModule
|
||||
_log = logging.getLogger("mitx.courseware")
|
||||
|
||||
|
||||
def grade_sheet(student, course, student_module_cache):
|
||||
def grade_sheet(student, course, grader, student_module_cache):
|
||||
"""
|
||||
This pulls a summary of all problems in the course. It returns a dictionary with two datastructures:
|
||||
|
||||
@@ -78,7 +77,6 @@ def grade_sheet(student, course, student_module_cache):
|
||||
'chapter': c.metadata.get('display_name'),
|
||||
'sections': sections})
|
||||
|
||||
grader = course_settings.GRADER
|
||||
grade_summary = grader.grade(totaled_scores)
|
||||
|
||||
return {'courseware_summary': chapters,
|
||||
|
||||
@@ -19,7 +19,6 @@ from django.views.decorators.cache import cache_control
|
||||
from module_render import toc_for_course, get_module, get_section
|
||||
from models import StudentModuleCache
|
||||
from student.models import UserProfile
|
||||
from multicourse import multicourse_settings
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -110,8 +109,8 @@ def profile(request, course_id, student_id=None):
|
||||
user_info = UserProfile.objects.get(user=student)
|
||||
|
||||
student_module_cache = StudentModuleCache(request.user, course)
|
||||
course, _, _, _ = get_module(request.user, request, course.location, student_module_cache)
|
||||
|
||||
course_module, _, _, _ = get_module(request.user, request, course.location, student_module_cache)
|
||||
|
||||
context = {'name': user_info.name,
|
||||
'username': student.username,
|
||||
'location': user_info.location,
|
||||
@@ -121,7 +120,7 @@ def profile(request, course_id, student_id=None):
|
||||
'format_url_params': format_url_params,
|
||||
'csrf': csrf(request)['csrf_token']
|
||||
}
|
||||
context.update(grades.grade_sheet(student, course, student_module_cache))
|
||||
context.update(grades.grade_sheet(student, course_module, course.grader, student_module_cache))
|
||||
|
||||
return render_to_response('profile.html', context)
|
||||
|
||||
@@ -184,9 +183,6 @@ def index(request, course_id, chapter=None, section=None,
|
||||
chapter = clean(chapter)
|
||||
section = clean(section)
|
||||
|
||||
if settings.ENABLE_MULTICOURSE:
|
||||
settings.MODULESTORE['default']['OPTIONS']['data_dir'] = settings.DATA_DIR + multicourse_settings.get_course_xmlpath(course)
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'accordion': render_accordion(request, course, chapter, section),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<%block name="headextra">
|
||||
<%static:css group='course'/>
|
||||
</%block>
|
||||
|
||||
<%namespace name="profile_graphs" file="profile_graphs.js"/>
|
||||
|
||||
<%block name="title"><title>Profile - edX 6.002x</title></%block>
|
||||
@@ -110,9 +111,9 @@ $(function() {
|
||||
</%block>
|
||||
|
||||
|
||||
<%include file="navigation.html" args="active_page='profile'" />
|
||||
<%include file="course_navigation.html" args="active_page='profile'" />
|
||||
|
||||
<section class="main-content">
|
||||
<section class="container">
|
||||
<div class="profile-wrapper">
|
||||
|
||||
<section class="course-info">
|
||||
@@ -126,8 +127,7 @@ $(function() {
|
||||
%for chapter in courseware_summary:
|
||||
%if not chapter['chapter'] == "hidden":
|
||||
<li>
|
||||
<h2><a href="${reverse('courseware_chapter', args=format_url_params([chapter['course'], chapter['chapter']])) }">
|
||||
${ chapter['chapter'] }</a></h2>
|
||||
<h2>${ chapter['chapter'] }</h2>
|
||||
|
||||
<ol class="sections">
|
||||
%for section in chapter['sections']:
|
||||
@@ -138,7 +138,7 @@ $(function() {
|
||||
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
|
||||
%>
|
||||
|
||||
<h3><a href="${reverse('courseware_section', args=format_url_params([chapter['course'], chapter['chapter'], section['section']])) }">
|
||||
<h3><a href="${reverse('courseware_section', kwargs={'course_id' : course.id, 'chapter' : chapter['chapter'], 'section' : section['section']})}">
|
||||
${ section['section'] }</a> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</h3>
|
||||
${section['format']}
|
||||
%if 'due' in section and section['due']!="":
|
||||
|
||||
Reference in New Issue
Block a user