Commit 01623ddb by Brittany Cheng

merge with origin master

parents c107d109 0bfcefd2
...@@ -37,7 +37,8 @@ REPOS = { ...@@ -37,7 +37,8 @@ REPOS = {
}, },
'content-mit-6002x': { 'content-mit-6002x': {
'branch': 'master', 'branch': 'master',
'origin': 'git@github.com:MITx/6002x-fall-2012.git', #'origin': 'git@github.com:MITx/6002x-fall-2012.git',
'origin': 'git@github.com:MITx/content-mit-6002x.git',
}, },
'6.00x': { '6.00x': {
'branch': 'master', 'branch': 'master',
......
...@@ -23,7 +23,8 @@ from django.http import HttpResponse, Http404 ...@@ -23,7 +23,8 @@ from django.http import HttpResponse, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from BeautifulSoup import BeautifulSoup #from BeautifulSoup import BeautifulSoup
from bs4 import BeautifulSoup
from django.core.cache import cache from django.core.cache import cache
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
......
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,
......
...@@ -23,6 +23,7 @@ from django.views.decorators.cache import cache_control ...@@ -23,6 +23,7 @@ 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 multicourse import multicourse_settings
from django_comment_client.utils import get_discussion_title from django_comment_client.utils import get_discussion_title
...@@ -121,8 +122,8 @@ def profile(request, course_id, student_id=None): ...@@ -121,8 +122,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,
...@@ -132,7 +133,7 @@ def profile(request, course_id, student_id=None): ...@@ -132,7 +133,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)
...@@ -195,9 +196,6 @@ def index(request, course_id, chapter=None, section=None, ...@@ -195,9 +196,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),
......
...@@ -91,11 +91,12 @@ $discussion_input_width: 90%; ...@@ -91,11 +91,12 @@ $discussion_input_width: 90%;
} }
} }
.discussion-votes { .discussion-votes {
margin-right: 4%; margin-right: 2%;
margin-top: 2%; margin-top: 2%;
text-align: center; text-align: center;
height: flex-grid(3); height: flex-grid(3);
float: left; float: left;
margin-left: 2%;
.discussion-vote-count { .discussion-vote-count {
font-size: $comment_body_size; font-size: $comment_body_size;
@include discussion-font; @include discussion-font;
...@@ -109,9 +110,9 @@ $discussion_input_width: 90%; ...@@ -109,9 +110,9 @@ $discussion_input_width: 90%;
margin-bottom: 3px; margin-bottom: 3px;
} }
&.discussion-vote-down { &.discussion-vote-down {
margin-top: 3px; margin-top: 5px;
} }
&.voted { &.voted {
color: #1d9dd9; color: #1d9dd9;
} }
} }
...@@ -202,9 +203,8 @@ $discussion_input_width: 90%; ...@@ -202,9 +203,8 @@ $discussion_input_width: 90%;
border: 1px solid #A5D24A; border: 1px solid #A5D24A;
-moz-border-radius: 2px; -moz-border-radius: 2px;
-webkit-border-radius: 2px; -webkit-border-radius: 2px;
display: block;
float: left; float: left;
padding: 5px; padding: 5px 7px;
text-decoration: none; text-decoration: none;
background: #CDE69C; background: #CDE69C;
color: #638421; color: #638421;
...@@ -268,10 +268,6 @@ $discussion_input_width: 90%; ...@@ -268,10 +268,6 @@ $discussion_input_width: 90%;
color: black; color: black;
} }
} }
.discussion-votes {
margin-right: 6px;
margin-top: 6px;
}
} }
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
.notifications { .notifications {
@include news-font; @include news-font;
font-size: 0.9em;
padding-left: 20px; padding-left: 20px;
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
<section class="container"> <section class="container">
<div class="course-wrapper"> <div class="course-wrapper">
<section class="course-content"> <section class="course-content">
<h3> Updates to Discussion Posts You Follow </h3>
${content} ${content}
</section> </section>
</div> </div>
......
...@@ -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="javascript:void(0)"> <h2>${ chapter['chapter'] }</h2>
${ chapter['chapter'] }</a></h2>
<ol class="sections"> <ol class="sections">
%for section in chapter['sections']: %for section in chapter['sections']:
...@@ -137,8 +137,8 @@ $(function() { ...@@ -137,8 +137,8 @@ $(function() {
total = section['section_total'].possible total = section['section_total'].possible
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', kwargs={'course_id' : course.id, 'chapter' : chapter['chapter'], 'section' : section['section']})}">
<h3><a href="javascript:void(0)"> >>>>>>> 7f2f47597fd50acbab49be5173b76b026ff4378d
${ 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']!="":
......
...@@ -13,7 +13,8 @@ django_debug_toolbar ...@@ -13,7 +13,8 @@ django_debug_toolbar
-e git://github.com/MITx/django-pipeline.git#egg=django-pipeline -e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
django-staticfiles>=1.2.1 django-staticfiles>=1.2.1
fs fs
beautifulsoup beautifulsoup
beautifulsoup4
feedparser feedparser
requests requests
sympy sympy
......
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