Commit ae3da772 by Bridger Maxwell

Got profile page working again. The grader for each course is now defined in the…

Got profile page working again. The grader for each course is now defined in the data dir as grading_policy.json.
parent fafa86b4
from fs.errors import ResourceNotFoundError
import time import time
import dateutil.parser import dateutil.parser
import logging import logging
from xmodule.graders import load_grading_policy
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.seq_module import SequenceDescriptor, SequenceModule
...@@ -14,6 +16,9 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -14,6 +16,9 @@ class CourseDescriptor(SequenceDescriptor):
def __init__(self, system, definition=None, **kwargs): def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs) super(CourseDescriptor, self).__init__(system, definition, **kwargs)
self._grader = None
self._grade_cutoffs = None
try: try:
self.start = time.strptime(self.metadata["start"], "%Y-%m-%dT%H:%M") self.start = time.strptime(self.metadata["start"], "%Y-%m-%dT%H:%M")
...@@ -27,7 +32,34 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -27,7 +32,34 @@ class CourseDescriptor(SequenceDescriptor):
def has_started(self): def has_started(self):
return time.gmtime() > self.start 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 @staticmethod
def id_to_location(course_id): def id_to_location(course_id):
'''Convert the given course_id (org/course/name) to a location object. '''Convert the given course_id (org/course/name) to a location object.
......
import abc import abc
import json
import logging import logging
from collections import namedtuple from collections import namedtuple
...@@ -9,6 +10,69 @@ log = logging.getLogger("mitx.courseware") ...@@ -9,6 +10,69 @@ log = logging.getLogger("mitx.courseware")
# Section either indicates the name of the problem or the name of the section # Section either indicates the name of the problem or the name of the section
Score = namedtuple("Score", "earned possible graded 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"): def aggregate_scores(scores, section_name="summary"):
""" """
......
"""
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 ...@@ -3,7 +3,6 @@ import logging
from django.conf import settings from django.conf import settings
from courseware.course_settings import course_settings
from xmodule import graders from xmodule import graders
from xmodule.graders import Score from xmodule.graders import Score
from models import StudentModule from models import StudentModule
...@@ -11,7 +10,7 @@ from models import StudentModule ...@@ -11,7 +10,7 @@ from models import StudentModule
_log = logging.getLogger("mitx.courseware") _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: 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): ...@@ -78,7 +77,6 @@ def grade_sheet(student, course, student_module_cache):
'chapter': c.metadata.get('display_name'), 'chapter': c.metadata.get('display_name'),
'sections': sections}) 'sections': sections})
grader = course_settings.GRADER
grade_summary = grader.grade(totaled_scores) grade_summary = grader.grade(totaled_scores)
return {'courseware_summary': chapters, return {'courseware_summary': chapters,
......
...@@ -19,7 +19,6 @@ from django.views.decorators.cache import cache_control ...@@ -19,7 +19,6 @@ from django.views.decorators.cache import cache_control
from module_render import toc_for_course, get_module, get_section from module_render import toc_for_course, get_module, get_section
from models import StudentModuleCache from models import StudentModuleCache
from student.models import UserProfile from student.models import UserProfile
from multicourse import multicourse_settings
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -110,8 +109,8 @@ def profile(request, course_id, student_id=None): ...@@ -110,8 +109,8 @@ def profile(request, course_id, student_id=None):
user_info = UserProfile.objects.get(user=student) user_info = UserProfile.objects.get(user=student)
student_module_cache = StudentModuleCache(request.user, course) 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, context = {'name': user_info.name,
'username': student.username, 'username': student.username,
'location': user_info.location, 'location': user_info.location,
...@@ -121,7 +120,7 @@ def profile(request, course_id, student_id=None): ...@@ -121,7 +120,7 @@ def profile(request, course_id, student_id=None):
'format_url_params': format_url_params, 'format_url_params': format_url_params,
'csrf': csrf(request)['csrf_token'] '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) return render_to_response('profile.html', context)
...@@ -184,9 +183,6 @@ def index(request, course_id, chapter=None, section=None, ...@@ -184,9 +183,6 @@ def index(request, course_id, chapter=None, section=None,
chapter = clean(chapter) chapter = clean(chapter)
section = clean(section) section = clean(section)
if settings.ENABLE_MULTICOURSE:
settings.MODULESTORE['default']['OPTIONS']['data_dir'] = settings.DATA_DIR + multicourse_settings.get_course_xmlpath(course)
context = { context = {
'csrf': csrf(request)['csrf_token'], 'csrf': csrf(request)['csrf_token'],
'accordion': render_accordion(request, course, chapter, section), 'accordion': render_accordion(request, course, chapter, section),
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<%block name="headextra"> <%block name="headextra">
<%static:css group='course'/> <%static:css group='course'/>
</%block> </%block>
<%namespace name="profile_graphs" file="profile_graphs.js"/> <%namespace name="profile_graphs" file="profile_graphs.js"/>
<%block name="title"><title>Profile - edX 6.002x</title></%block> <%block name="title"><title>Profile - edX 6.002x</title></%block>
...@@ -110,9 +111,9 @@ $(function() { ...@@ -110,9 +111,9 @@ $(function() {
</%block> </%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"> <div class="profile-wrapper">
<section class="course-info"> <section class="course-info">
...@@ -126,8 +127,7 @@ $(function() { ...@@ -126,8 +127,7 @@ $(function() {
%for chapter in courseware_summary: %for chapter in courseware_summary:
%if not chapter['chapter'] == "hidden": %if not chapter['chapter'] == "hidden":
<li> <li>
<h2><a href="${reverse('courseware_chapter', args=format_url_params([chapter['course'], chapter['chapter']])) }"> <h2>${ chapter['chapter'] }</h2>
${ chapter['chapter'] }</a></h2>
<ol class="sections"> <ol class="sections">
%for section in chapter['sections']: %for section in chapter['sections']:
...@@ -138,7 +138,7 @@ $(function() { ...@@ -138,7 +138,7 @@ $(function() {
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else "" 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['section'] }</a> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</h3>
${section['format']} ${section['format']}
%if 'due' in section and section['due']!="": %if 'due' in section and section['due']!="":
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment