diff --git a/cms/static/sass/_courseware.scss b/cms/static/sass/_courseware.scss
index f2bd25c601..45ea111b6f 100644
--- a/cms/static/sass/_courseware.scss
+++ b/cms/static/sass/_courseware.scss
@@ -498,6 +498,7 @@ input.courseware-unit-search-input {
}
&.new-section {
+
header {
height: auto;
@include clearfix();
@@ -506,6 +507,15 @@ input.courseware-unit-search-input {
.expand-collapse-icon {
visibility: hidden;
}
+
+ .item-details {
+ padding: 25px 0 0 0;
+
+ .section-name {
+ float: none;
+ width: 100%;
+ }
+ }
}
}
diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py
index eaea908f3a..ee69d925d0 100644
--- a/common/lib/xmodule/xmodule/combined_open_ended_module.py
+++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py
@@ -108,11 +108,13 @@ class CombinedOpenEndedModule(XModule):
instance_state = {}
self.version = self.metadata.get('version', DEFAULT_VERSION)
+ version_error_string = "Version of combined open ended module {0} is not correct. Going with version {1}"
if not isinstance(self.version, basestring):
try:
self.version = str(self.version)
except:
- log.error("Version {0} is not correct. Going with version {1}".format(self.version, DEFAULT_VERSION))
+ #This is a dev_facing_error
+ log.info(version_error_string.format(self.version, DEFAULT_VERSION))
self.version = DEFAULT_VERSION
versions = [i[0] for i in VERSION_TUPLES]
@@ -122,7 +124,8 @@ class CombinedOpenEndedModule(XModule):
try:
version_index = versions.index(self.version)
except:
- log.error("Version {0} is not correct. Going with version {1}".format(self.version, DEFAULT_VERSION))
+ #This is a dev_facing_error
+ log.error(version_error_string.format(self.version, DEFAULT_VERSION))
self.version = DEFAULT_VERSION
version_index = versions.index(self.version)
diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
index 931e3bf25d..d38036c8de 100644
--- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -107,6 +107,8 @@ class @CombinedOpenEnded
@can_upload_files = false
@open_ended_child= @$('.open-ended-child')
+ @out_of_sync_message = 'The problem state got out of sync. Try reloading the page.'
+
if @task_number>1
@prompt_hide()
else if @task_number==1 and @child_state!='initial'
@@ -314,7 +316,7 @@ class @CombinedOpenEnded
$.ajaxWithPrefix("#{@ajax_url}/save_answer",settings)
else
- @errors_area.html('Problem state got out of sync. Try reloading the page.')
+ @errors_area.html(@out_of_sync_message)
save_assessment: (event) =>
event.preventDefault()
@@ -336,7 +338,7 @@ class @CombinedOpenEnded
else
@errors_area.html(response.error)
else
- @errors_area.html('Problem state got out of sync. Try reloading the page.')
+ @errors_area.html(@out_of_sync_message)
save_hint: (event) =>
event.preventDefault()
@@ -351,7 +353,7 @@ class @CombinedOpenEnded
else
@errors_area.html(response.error)
else
- @errors_area.html('Problem state got out of sync. Try reloading the page.')
+ @errors_area.html(@out_of_sync_message)
skip_post_assessment: =>
if @child_state == 'post_assessment'
@@ -363,7 +365,7 @@ class @CombinedOpenEnded
else
@errors_area.html(response.error)
else
- @errors_area.html('Problem state got out of sync. Try reloading the page.')
+ @errors_area.html(@out_of_sync_message)
reset: (event) =>
event.preventDefault()
@@ -383,7 +385,7 @@ class @CombinedOpenEnded
else
@errors_area.html(response.error)
else
- @errors_area.html('Problem state got out of sync. Try reloading the page.')
+ @errors_area.html(@out_of_sync_message)
next_problem: =>
if @child_state == 'done'
@@ -406,7 +408,7 @@ class @CombinedOpenEnded
else
@errors_area.html(response.error)
else
- @errors_area.html('Problem state got out of sync. Try reloading the page.')
+ @errors_area.html(@out_of_sync_message)
gentle_alert: (msg) =>
if @el.find('.open-ended-alert').length
diff --git a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
index 7f85ea050e..0b38090e43 100644
--- a/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
+++ b/common/lib/xmodule/xmodule/js/src/peergrading/peer_grading_problem.coffee
@@ -175,6 +175,7 @@ class @PeerGradingProblem
@prompt_container = $('.prompt-container')
@rubric_container = $('.rubric-container')
@flag_student_container = $('.flag-student-container')
+ @answer_unknown_container = $('.answer-unknown-container')
@calibration_panel = $('.calibration-panel')
@grading_panel = $('.grading-panel')
@content_panel = $('.content-panel')
@@ -208,6 +209,7 @@ class @PeerGradingProblem
@interstitial_page_button = $('.interstitial-page-button')
@calibration_interstitial_page_button = $('.calibration-interstitial-page-button')
@flag_student_checkbox = $('.flag-checkbox')
+ @answer_unknown_checkbox = $('.answer-unknown-checkbox')
@collapse_question()
Collapsible.setCollapsibles(@content_panel)
@@ -262,6 +264,7 @@ class @PeerGradingProblem
submission_key: @submission_key_input.val()
feedback: @feedback_area.val()
submission_flagged: @flag_student_checkbox.is(':checked')
+ answer_unknown: @answer_unknown_checkbox.is(':checked')
return data
@@ -360,6 +363,8 @@ class @PeerGradingProblem
@calibration_panel.find('.grading-text').hide()
@grading_panel.find('.grading-text').hide()
@flag_student_container.hide()
+ @answer_unknown_container.hide()
+
@feedback_area.val("")
@submit_button.unbind('click')
@@ -388,6 +393,7 @@ class @PeerGradingProblem
@calibration_panel.find('.grading-text').show()
@grading_panel.find('.grading-text').show()
@flag_student_container.show()
+ @answer_unknown_container.show()
@feedback_area.val("")
@submit_button.unbind('click')
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
index 828afb32a7..171441c562 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
@@ -19,7 +19,7 @@ log = logging.getLogger("mitx.courseware")
# Set the default number of max attempts. Should be 1 for production
# Set higher for debugging/testing
# attempts specified in xml definition overrides this.
-MAX_ATTEMPTS = 10000
+MAX_ATTEMPTS = 1
# Set maximum available number of points.
# Overriden by max_score specified in xml.
@@ -149,6 +149,7 @@ class CombinedOpenEndedV1Module():
self.skip_basic_checks = self.metadata.get('skip_spelling_checks', SKIP_BASIC_CHECKS)
display_due_date_string = self.metadata.get('due', None)
+
grace_period_string = self.metadata.get('graceperiod', None)
try:
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
@@ -646,7 +647,10 @@ class CombinedOpenEndedV1Module():
if self.attempts > self.max_attempts:
return {
'success': False,
- 'error': 'Too many attempts.'
+ #This is a student_facing_error
+ 'error': ('You have attempted this question {0} times. '
+ 'You are only allowed to attempt it {1} times.').format(
+ self.attempts, self.max_attempts)
}
self.state = self.INITIAL
self.allow_reset = False
@@ -785,7 +789,8 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
expected_children = ['task', 'rubric', 'prompt']
for child in expected_children:
if len(xml_object.xpath(child)) == 0:
- raise ValueError("Combined Open Ended definition must include at least one '{0}' tag".format(child))
+ #This is a staff_facing_error
+ raise ValueError("Combined Open Ended definition must include at least one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
def parse_task(k):
"""Assumes that xml_object has child k"""
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py
index 7c00c5f029..f756b2b853 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py
@@ -4,7 +4,6 @@ from lxml import etree
log = logging.getLogger(__name__)
GRADER_TYPE_IMAGE_DICT = {
- '8B' : '/static/images/random_grading_icon.png',
'SA' : '/static/images/self_assessment_icon.png',
'PE' : '/static/images/peer_grading_icon.png',
'ML' : '/static/images/ml_grading_icon.png',
@@ -13,7 +12,6 @@ GRADER_TYPE_IMAGE_DICT = {
}
HUMAN_GRADER_TYPE = {
- '8B' : 'Magic-8-Ball-Assessment',
'SA' : 'Self-Assessment',
'PE' : 'Peer-Assessment',
'IN' : 'Instructor-Assessment',
@@ -71,8 +69,9 @@ class CombinedOpenEndedRubric(object):
})
success = True
except:
- error_message = "[render_rubric] Could not parse the rubric with xml: {0}".format(rubric_xml)
- log.error(error_message)
+ #This is a staff_facing_error
+ error_message = "[render_rubric] Could not parse the rubric with xml: {0}. Contact the learning sciences group for assistance.".format(rubric_xml)
+ log.exception(error_message)
raise RubricParsingError(error_message)
return {'success' : success, 'html' : html, 'rubric_scores' : rubric_scores}
@@ -81,7 +80,8 @@ class CombinedOpenEndedRubric(object):
success = rubric_dict['success']
rubric_feedback = rubric_dict['html']
if not success:
- error_message = "Could not parse rubric : {0} for location {1}".format(rubric_string, location.url())
+ #This is a staff_facing_error
+ error_message = "Could not parse rubric : {0} for location {1}. Contact the learning sciences group for assistance.".format(rubric_string, location.url())
log.error(error_message)
raise RubricParsingError(error_message)
@@ -90,13 +90,15 @@ class CombinedOpenEndedRubric(object):
for category in rubric_categories:
total = total + len(category['options']) - 1
if len(category['options']) > (max_score_allowed + 1):
- error_message = "Number of score points in rubric {0} higher than the max allowed, which is {1}".format(
+ #This is a staff_facing_error
+ error_message = "Number of score points in rubric {0} higher than the max allowed, which is {1}. Contact the learning sciences group for assistance.".format(
len(category['options']), max_score_allowed)
log.error(error_message)
raise RubricParsingError(error_message)
if total != max_score:
- error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}".format(
+ #This is a staff_facing_error
+ error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}. Contact the learning sciences group for assistance.".format(
max_score, location, total)
log.error(error_msg)
raise RubricParsingError(error_msg)
@@ -118,7 +120,8 @@ class CombinedOpenEndedRubric(object):
categories = []
for category in element:
if category.tag != 'category':
- raise RubricParsingError("[extract_categories] Expected a tag: got {0} instead".format(category.tag))
+ #This is a staff_facing_error
+ raise RubricParsingError("[extract_categories] Expected a tag: got {0} instead. Contact the learning sciences group for assistance.".format(category.tag))
else:
categories.append(self.extract_category(category))
return categories
@@ -144,12 +147,14 @@ class CombinedOpenEndedRubric(object):
self.has_score = True
# if we are missing the score tag and we are expecting one
elif self.has_score:
- raise RubricParsingError("[extract_category] Category {0} is missing a score".format(descriptionxml.text))
+ #This is a staff_facing_error
+ raise RubricParsingError("[extract_category] Category {0} is missing a score. Contact the learning sciences group for assistance.".format(descriptionxml.text))
# parse description
if descriptionxml.tag != 'description':
- raise RubricParsingError("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag))
+ #This is a staff_facing_error
+ raise RubricParsingError("[extract_category]: expected description tag, got {0} instead. Contact the learning sciences group for assistance.".format(descriptionxml.tag))
description = descriptionxml.text
@@ -159,7 +164,8 @@ class CombinedOpenEndedRubric(object):
# parse options
for option in optionsxml:
if option.tag != 'option':
- raise RubricParsingError("[extract_category]: expected option tag, got {0} instead".format(option.tag))
+ #This is a staff_facing_error
+ raise RubricParsingError("[extract_category]: expected option tag, got {0} instead. Contact the learning sciences group for assistance.".format(option.tag))
else:
pointstr = option.get("points")
if pointstr:
@@ -168,7 +174,8 @@ class CombinedOpenEndedRubric(object):
try:
points = int(pointstr)
except ValueError:
- raise RubricParsingError("[extract_category]: expected points to have int, got {0} instead".format(pointstr))
+ #This is a staff_facing_error
+ raise RubricParsingError("[extract_category]: expected points to have int, got {0} instead. Contact the learning sciences group for assistance.".format(pointstr))
elif autonumbering:
# use the generated one if we're in the right mode
points = cur_points
@@ -200,7 +207,6 @@ class CombinedOpenEndedRubric(object):
for grader_type in tuple[3]:
rubric_categories[i]['options'][j]['grader_types'].append(grader_type)
- log.debug(rubric_categories)
html = self.system.render_template('open_ended_combined_rubric.html',
{'categories': rubric_categories,
'has_score': True,
@@ -219,13 +225,15 @@ class CombinedOpenEndedRubric(object):
Validates a set of options. This can and should be extended to filter out other bad edge cases
'''
if len(options) == 0:
- raise RubricParsingError("[extract_category]: no options associated with this category")
+ #This is a staff_facing_error
+ raise RubricParsingError("[extract_category]: no options associated with this category. Contact the learning sciences group for assistance.")
if len(options) == 1:
return
prev = options[0]['points']
for option in options[1:]:
if prev == option['points']:
- raise RubricParsingError("[extract_category]: found duplicate point values between two different options")
+ #This is a staff_facing_error
+ raise RubricParsingError("[extract_category]: found duplicate point values between two different options. Contact the learning sciences group for assistance.")
else:
prev = option['points']
@@ -241,11 +249,14 @@ class CombinedOpenEndedRubric(object):
"""
success = False
if len(scores)==0:
- log.error("Score length is 0.")
+ #This is a dev_facing_error
+ log.error("Score length is 0 when trying to reformat rubric scores for rendering.")
return success, ""
if len(scores) != len(score_types) or len(feedback_types) != len(scores):
- log.error("Length mismatches.")
+ #This is a dev_facing_error
+ log.error("Length mismatches when trying to reformat rubric scores for rendering. "
+ "Scores: {0}, Score Types: {1} Feedback Types: {2}".format(scores, score_types, feedback_types))
return success, ""
score_lists = []
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/grading_service_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/grading_service_module.py
index 6bd7a6fd0e..8a4caa1291 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/grading_service_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/grading_service_module.py
@@ -51,6 +51,8 @@ class GradingService(object):
r = self._try_with_login(op)
except (RequestException, ConnectionError, HTTPError) as err:
# reraise as promised GradingServiceError, but preserve stacktrace.
+ #This is a dev_facing_error
+ log.error("Problem posting data to the grading controller. URL: {0}, data: {1}".format(url, data))
raise GradingServiceError, str(err), sys.exc_info()[2]
return r.text
@@ -67,6 +69,8 @@ class GradingService(object):
r = self._try_with_login(op)
except (RequestException, ConnectionError, HTTPError) as err:
# reraise as promised GradingServiceError, but preserve stacktrace.
+ #This is a dev_facing_error
+ log.error("Problem getting data from the grading controller. URL: {0}, params: {1}".format(url, params))
raise GradingServiceError, str(err), sys.exc_info()[2]
return r.text
@@ -119,11 +123,13 @@ class GradingService(object):
return response_json
# if we can't parse the rubric into HTML,
except etree.XMLSyntaxError, RubricParsingError:
+ #This is a dev_facing_error
log.exception("Cannot parse rubric string. Raw string: {0}"
.format(rubric))
return {'success': False,
'error': 'Error displaying submission'}
except ValueError:
+ #This is a dev_facing_error
log.exception("Error parsing response: {0}".format(response))
return {'success': False,
'error': "Error displaying submission"}
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py
index 88921c1429..edae69854f 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py
@@ -251,8 +251,9 @@ def upload_to_s3(file_to_upload, keyname, s3_interface):
return True, public_url
except:
- error_message = "Could not connect to S3."
- log.exception(error_message)
+ #This is a dev_facing_error
+ error_message = "Could not connect to S3 to upload peer grading image. Trying to utilize bucket: {0}".format(bucketname.lower())
+ log.error(error_message)
return False, error_message
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
index 95c631c8fd..0b546482f4 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
@@ -59,12 +59,14 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
self.submission_id = None
self.grader_id = None
+ error_message = "No {0} found in problem xml for open ended problem. Contact the learning sciences group for assistance."
if oeparam is None:
- raise ValueError("No oeparam found in problem xml.")
+ #This is a staff_facing_error
+ raise ValueError(error_message.format('oeparam'))
if self.prompt is None:
- raise ValueError("No prompt found in problem xml.")
+ raise ValueError(error_message.format('prompt'))
if self.rubric is None:
- raise ValueError("No rubric found in problem xml.")
+ raise ValueError(error_message.format('rubric'))
self._parse(oeparam, self.prompt, self.rubric, system)
@@ -73,6 +75,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
self.send_to_grader(self.latest_answer(), system)
self.created = False
+
def _parse(self, oeparam, prompt, rubric, system):
'''
Parse OpenEndedResponse XML:
@@ -98,13 +101,14 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
# __init__ adds it (easiest way to get problem location into
# response types)
except TypeError, ValueError:
- log.exception("Grader payload %r is not a json object!", grader_payload)
+ #This is a dev_facing_error
+ log.exception("Grader payload from external open ended grading server is not a json object! Object: {0}".format(grader_payload))
self.initial_display = find_with_default(oeparam, 'initial_display', '')
self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.')
parsed_grader_payload.update({
- 'location': system.location.url(),
+ 'location': self.location_string,
'course_id': system.course_id,
'prompt': prompt_string,
'rubric': rubric_string,
@@ -134,24 +138,27 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
"""
event_info = dict()
- event_info['problem_id'] = system.location.url()
+ event_info['problem_id'] = self.location_string
event_info['student_id'] = system.anonymous_student_id
event_info['survey_responses'] = get
survey_responses = event_info['survey_responses']
for tag in ['feedback', 'submission_id', 'grader_id', 'score']:
if tag not in survey_responses:
- return {'success': False, 'msg': "Could not find needed tag {0}".format(tag)}
+ #This is a student_facing_error
+ return {'success': False, 'msg': "Could not find needed tag {0} in the survey responses. Please try submitting again.".format(tag)}
try:
submission_id = int(survey_responses['submission_id'])
grader_id = int(survey_responses['grader_id'])
feedback = str(survey_responses['feedback'].encode('ascii', 'ignore'))
score = int(survey_responses['score'])
except:
+ #This is a dev_facing_error
error_message = ("Could not parse submission id, grader id, "
"or feedback from message_post ajax call. Here is the message data: {0}".format(
survey_responses))
log.exception(error_message)
+ #This is a student_facing_error
return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."}
qinterface = system.xqueue['interface']
@@ -188,6 +195,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
self.state = self.DONE
+ #This is a student_facing_message
return {'success': success, 'msg': "Successfully submitted your feedback."}
def send_to_grader(self, submission, system):
@@ -337,18 +345,22 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
for tag in ['success', 'feedback', 'submission_id', 'grader_id']:
if tag not in response_items:
- return format_feedback('errors', 'Error getting feedback')
+ #This is a student_facing_error
+ return format_feedback('errors', 'Error getting feedback from grader.')
feedback_items = response_items['feedback']
try:
feedback = json.loads(feedback_items)
except (TypeError, ValueError):
- log.exception("feedback_items have invalid json %r", feedback_items)
- return format_feedback('errors', 'Could not parse feedback')
+ #This is a dev_facing_error
+ log.exception("feedback_items from external open ended grader have invalid json {0}".format(feedback_items))
+ #This is a student_facing_error
+ return format_feedback('errors', 'Error getting feedback from grader.')
if response_items['success']:
if len(feedback) == 0:
- return format_feedback('errors', 'No feedback available')
+ #This is a student_facing_error
+ return format_feedback('errors', 'No feedback available from grader.')
for tag in do_not_render:
if tag in feedback:
@@ -357,6 +369,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
feedback_lst = sorted(feedback.items(), key=get_priority)
feedback_list_part1 = u"\n".join(format_feedback(k, v) for k, v in feedback_lst)
else:
+ #This is a student_facing_error
feedback_list_part1 = format_feedback('errors', response_items['feedback'])
feedback_list_part2 = (u"\n".join([format_feedback_hidden(feedback_type, value)
@@ -432,14 +445,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
try:
score_result = json.loads(score_msg)
except (TypeError, ValueError):
- error_message = ("External grader message should be a JSON-serialized dict."
+ #This is a dev_facing_error
+ error_message = ("External open ended grader message should be a JSON-serialized dict."
" Received score_msg = {0}".format(score_msg))
log.error(error_message)
fail['feedback'] = error_message
return fail
if not isinstance(score_result, dict):
- error_message = ("External grader message should be a JSON-serialized dict."
+ #This is a dev_facing_error
+ error_message = ("External open ended grader message should be a JSON-serialized dict."
" Received score_result = {0}".format(score_result))
log.error(error_message)
fail['feedback'] = error_message
@@ -447,7 +462,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
for tag in ['score', 'feedback', 'grader_type', 'success', 'grader_id', 'submission_id']:
if tag not in score_result:
- error_message = ("External grader message is missing required tag: {0}"
+ #This is a dev_facing_error
+ error_message = ("External open ended grader message is missing required tag: {0}"
.format(tag))
log.error(error_message)
fail['feedback'] = error_message
@@ -564,7 +580,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
}
if dispatch not in handlers:
- return 'Error'
+ #This is a dev_facing_error
+ log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch))
+ #This is a dev_facing_error
+ return json.dumps({'error': 'Error handling action. Please try again.', 'success' : False})
before = self.get_progress()
d = handlers[dispatch](get, system)
@@ -605,15 +624,21 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
success, get = self.append_image_to_student_answer(get)
error_message = ""
if success:
- get['student_answer'] = OpenEndedModule.sanitize_html(get['student_answer'])
- self.new_history_entry(get['student_answer'])
- self.send_to_grader(get['student_answer'], system)
- self.change_state(self.ASSESSING)
+ success, allowed_to_submit, error_message = self.check_if_student_can_submit()
+ if allowed_to_submit:
+ get['student_answer'] = OpenEndedModule.sanitize_html(get['student_answer'])
+ self.new_history_entry(get['student_answer'])
+ self.send_to_grader(get['student_answer'], system)
+ self.change_state(self.ASSESSING)
+ else:
+ #Error message already defined
+ success = False
else:
+ #This is a student_facing_error
error_message = "There was a problem saving the image in your submission. Please try a different image, or try pasting a link to an image into the answer box."
return {
- 'success': True,
+ 'success': success,
'error': error_message,
'student_response': get['student_answer']
}
@@ -690,7 +715,8 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
"""
for child in ['openendedparam']:
if len(xml_object.xpath(child)) != 1:
- raise ValueError("Open Ended definition must include exactly one '{0}' tag".format(child))
+ #This is a staff_facing_error
+ raise ValueError("Open Ended definition must include exactly one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
def parse(k):
"""Assumes that xml_object has child k"""
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
index 2b90fdd848..b3d835c895 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
@@ -22,6 +22,7 @@ from xmodule.stringify import stringify_children
from xmodule.xml_module import XmlDescriptor
from xmodule.modulestore import Location
from capa.util import *
+from peer_grading_service import PeerGradingService
from datetime import datetime
@@ -105,6 +106,14 @@ class OpenEndedChild(object):
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
self._max_score = static_data['max_score']
+ self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system)
+
+ self.system = system
+ self.location_string = location
+ try:
+ self.location_string = self.location_string.url()
+ except:
+ pass
self.setup_response(system, location, definition, descriptor)
@@ -128,12 +137,14 @@ class OpenEndedChild(object):
if self.closed():
return True, {
'success': False,
- 'error': 'This problem is now closed.'
+ #This is a student_facing_error
+ 'error': 'The problem close date has passed, and this problem is now closed.'
}
elif self.attempts > self.max_attempts:
return True, {
'success': False,
- 'error': 'Too many attempts.'
+ #This is a student_facing_error
+ 'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(self.attempts, self.max_attempts)
}
else:
return False, {}
@@ -252,7 +263,8 @@ class OpenEndedChild(object):
try:
return Progress(self.get_score()['score'], self._max_score)
except Exception as err:
- log.exception("Got bad progress")
+ #This is a dev_facing_error
+ log.exception("Got bad progress from open ended child module. Max Score: {1}".format(self._max_score))
return None
return None
@@ -260,10 +272,12 @@ class OpenEndedChild(object):
"""
return dict out-of-sync error message, and also log.
"""
- log.warning("Assessment module state out sync. state: %r, get: %r. %s",
+ #This is a dev_facing_error
+ log.warning("Open ended child state out sync. state: %r, get: %r. %s",
self.state, get, msg)
+ #This is a student_facing_error
return {'success': False,
- 'error': 'The problem state got out-of-sync'}
+ 'error': 'The problem state got out-of-sync. Please try reloading the page.'}
def get_html(self):
"""
@@ -413,3 +427,34 @@ class OpenEndedChild(object):
success = True
return success, string
+
+ def check_if_student_can_submit(self):
+ location = self.location_string
+
+ student_id = self.system.anonymous_student_id
+ success = False
+ allowed_to_submit = True
+ response = {}
+ #This is a student_facing_error
+ error_string = ("You need to peer grade {0} more in order to make another submission. "
+ "You have graded {1}, and {2} are required. You have made {3} successful peer grading submissions.")
+ try:
+ response = self.peer_gs.get_data_for_location(location, student_id)
+ count_graded = response['count_graded']
+ count_required = response['count_required']
+ student_sub_count = response['student_sub_count']
+ success = True
+ except:
+ #This is a dev_facing_error
+ log.error("Could not contact external open ended graders for location {0} and student {1}".format(location,student_id))
+ #This is a student_facing_error
+ error_message = "Could not contact the graders. Please notify course staff."
+ return success, allowed_to_submit, error_message
+ if count_graded>=count_required:
+ return success, allowed_to_submit, ""
+ else:
+ allowed_to_submit = False
+ #This is a student_facing_error
+ error_message = error_string.format(count_required-count_graded, count_graded, count_required, student_sub_count)
+ return success, allowed_to_submit, error_message
+
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/peer_grading_service.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/peer_grading_service.py
index 5b639be4f4..42c54f0463 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/peer_grading_service.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/peer_grading_service.py
@@ -30,8 +30,8 @@ class PeerGradingService(GradingService):
self.system = system
def get_data_for_location(self, problem_location, student_id):
- response = self.get(self.get_data_for_location_url,
- {'location': problem_location, 'student_id': student_id})
+ params = {'location': problem_location, 'student_id': student_id}
+ response = self.get(self.get_data_for_location_url, params)
return self.try_to_decode(response)
def get_next_submission(self, problem_location, grader_id):
@@ -106,7 +106,7 @@ class MockPeerGradingService(object):
'max_score': 4})
def save_grade(self, location, grader_id, submission_id,
- score, feedback, submission_key):
+ score, feedback, submission_key, rubric_scores, submission_flagged):
return json.dumps({'success': True})
def is_student_calibrated(self, problem_location, grader_id):
@@ -122,7 +122,8 @@ class MockPeerGradingService(object):
'max_score': 4})
def save_calibration_essay(self, problem_location, grader_id,
- calibration_essay_id, submission_key, score, feedback):
+ calibration_essay_id, submission_key, score,
+ feedback, rubric_scores):
return {'success': True, 'actual_score': 2}
def get_problem_list(self, course_id, grader_id):
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
index c608eeea06..7ecb3c4d5e 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
@@ -90,7 +90,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
}
if dispatch not in handlers:
- return 'Error'
+ #This is a dev_facing_error
+ log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch))
+ #This is a dev_facing_error
+ return json.dumps({'error': 'Error handling action. Please try again.', 'success' : False})
before = self.get_progress()
d = handlers[dispatch](get, system)
@@ -123,7 +126,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
elif self.state in (self.POST_ASSESSMENT, self.DONE):
context['read_only'] = True
else:
- raise ValueError("Illegal state '%r'" % self.state)
+ #This is a dev_facing_error
+ raise ValueError("Self assessment module is in an illegal state '{0}'".format(self.state))
return system.render_template('self_assessment_rubric.html', context)
@@ -148,7 +152,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
elif self.state == self.DONE:
context['read_only'] = True
else:
- raise ValueError("Illegal state '%r'" % self.state)
+ #This is a dev_facing_error
+ raise ValueError("Self Assessment module is in an illegal state '{0}'".format(self.state))
return system.render_template('self_assessment_hint.html', context)
@@ -177,10 +182,16 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
# add new history element with answer and empty score and hint.
success, get = self.append_image_to_student_answer(get)
if success:
- get['student_answer'] = SelfAssessmentModule.sanitize_html(get['student_answer'])
- self.new_history_entry(get['student_answer'])
- self.change_state(self.ASSESSING)
+ success, allowed_to_submit, error_message = self.check_if_student_can_submit()
+ if allowed_to_submit:
+ get['student_answer'] = SelfAssessmentModule.sanitize_html(get['student_answer'])
+ self.new_history_entry(get['student_answer'])
+ self.change_state(self.ASSESSING)
+ else:
+ #Error message already defined
+ success = False
else:
+ #This is a student_facing_error
error_message = "There was a problem saving the image in your submission. Please try a different image, or try pasting a link to an image into the answer box."
return {
@@ -214,7 +225,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
for i in xrange(0,len(score_list)):
score_list[i] = int(score_list[i])
except ValueError:
- return {'success': False, 'error': "Non-integer score value, or no score list"}
+ #This is a dev_facing_error
+ log.error("Non-integer score value passed to save_assessment ,or no score list present.")
+ #This is a student_facing_error
+ return {'success': False, 'error': "Error saving your score. Please notify course staff."}
#Record score as assessment and rubric scores as post assessment
self.record_latest_score(score)
@@ -256,6 +270,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
try:
rubric_scores = json.loads(latest_post_assessment)
except:
+ #This is a dev_facing_error
log.error("Cannot parse rubric scores in self assessment module from {0}".format(latest_post_assessment))
rubric_scores = []
return [rubric_scores]
@@ -287,7 +302,8 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
expected_children = []
for child in expected_children:
if len(xml_object.xpath(child)) != 1:
- raise ValueError("Self assessment definition must include exactly one '{0}' tag".format(child))
+ #This is a staff_facing_error
+ raise ValueError("Self assessment definition must include exactly one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
def parse(k):
"""Assumes that xml_object has child k"""
diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index 4dfe8e0dfa..e262db5615 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -24,6 +24,8 @@ TRUE_DICT = [True, "True", "true", "TRUE"]
MAX_SCORE = 1
IS_GRADED = True
+EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please notify course staff."
+
class PeerGradingModule(XModule):
_VERSION = 1
@@ -145,7 +147,10 @@ class PeerGradingModule(XModule):
}
if dispatch not in handlers:
- return 'Error'
+ #This is a dev_facing_error
+ log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch))
+ #This is a dev_facing_error
+ return json.dumps({'error': 'Error handling action. Please try again.', 'success' : False})
d = handlers[dispatch](get)
@@ -163,6 +168,7 @@ class PeerGradingModule(XModule):
count_required = response['count_required']
success = True
except GradingServiceError:
+ #This is a dev_facing_error
log.exception("Error getting location data from controller for location {0}, student {1}"
.format(location, student_id))
@@ -188,6 +194,7 @@ class PeerGradingModule(XModule):
count_graded = response['count_graded']
count_required = response['count_required']
if count_required > 0 and count_graded >= count_required:
+ #Ensures that once a student receives a final score for peer grading, that it does not change.
self.student_data_for_location = response
score_dict = {
@@ -237,10 +244,12 @@ class PeerGradingModule(XModule):
response = self.peer_gs.get_next_submission(location, grader_id)
return response
except GradingServiceError:
+ #This is a dev_facing_error
log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
.format(self.peer_gs.url, location, grader_id))
+ #This is a student_facing_error
return {'success': False,
- 'error': 'Could not connect to grading service'}
+ 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
def save_grade(self, get):
"""
@@ -277,14 +286,16 @@ class PeerGradingModule(XModule):
score, feedback, submission_key, rubric_scores, submission_flagged)
return response
except GradingServiceError:
- log.exception("""Error saving grade. server url: {0}, location: {1}, submission_id:{2},
+ #This is a dev_facing_error
+ log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2},
submission_key: {3}, score: {4}"""
.format(self.peer_gs.url,
location, submission_id, submission_key, score)
)
+ #This is a student_facing_error
return {
'success': False,
- 'error': 'Could not connect to grading service'
+ 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
}
def is_student_calibrated(self, get):
@@ -317,11 +328,13 @@ class PeerGradingModule(XModule):
response = self.peer_gs.is_student_calibrated(location, grader_id)
return response
except GradingServiceError:
- log.exception("Error from grading service. server url: {0}, grader_id: {0}, location: {1}"
+ #This is a dev_facing_error
+ log.exception("Error from open ended grading service. server url: {0}, grader_id: {0}, location: {1}"
.format(self.peer_gs.url, grader_id, location))
+ #This is a student_facing_error
return {
'success': False,
- 'error': 'Could not connect to grading service'
+ 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR
}
def show_calibration_essay(self, get):
@@ -360,16 +373,20 @@ class PeerGradingModule(XModule):
response = self.peer_gs.show_calibration_essay(location, grader_id)
return response
except GradingServiceError:
- log.exception("Error from grading service. server url: {0}, location: {0}"
+ #This is a dev_facing_error
+ log.exception("Error from open ended grading service. server url: {0}, location: {0}"
.format(self.peer_gs.url, location))
+ #This is a student_facing_error
return {'success': False,
- 'error': 'Could not connect to grading service'}
+ 'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
# if we can't parse the rubric into HTML,
except etree.XMLSyntaxError:
+ #This is a dev_facing_error
log.exception("Cannot parse rubric string. Raw string: {0}"
.format(rubric))
+ #This is a student_facing_error
return {'success': False,
- 'error': 'Error displaying submission'}
+ 'error': 'Error displaying submission. Please notify course staff.'}
def save_calibration_essay(self, get):
@@ -408,8 +425,10 @@ class PeerGradingModule(XModule):
submission_key, score, feedback, rubric_scores)
return response
except GradingServiceError:
+ #This is a dev_facing_error
log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
- return self._err_response('Could not connect to grading service')
+ #This is a student_facing_error
+ return self._err_response('There was an error saving your score. Please notify course staff.')
def peer_grading_closed(self):
'''
@@ -440,11 +459,13 @@ class PeerGradingModule(XModule):
problem_list = problem_list_dict['problem_list']
except GradingServiceError:
- error_text = "Error occured while contacting the grading service"
+ #This is a student_facing_error
+ error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR
success = False
# catch error if if the json loads fails
except ValueError:
- error_text = "Could not get problem list"
+ #This is a student_facing_error
+ error_text = "Could not get list of problems to peer grade. Please notify course staff."
success = False
@@ -502,6 +523,8 @@ class PeerGradingModule(XModule):
if get == None or get.get('location') == None:
if not self.use_for_single_location:
#This is an error case, because it must be set to use a single location to be called without get parameters
+ #This is a dev_facing_error
+ log.error("Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.")
return {'html': "", 'success': False}
problem_location = self.link_to_location
@@ -566,7 +589,8 @@ class PeerGradingDescriptor(XmlDescriptor, EditingDescriptor):
expected_children = []
for child in expected_children:
if len(xml_object.xpath(child)) == 0:
- raise ValueError("Peer grading definition must include at least one '{0}' tag".format(child))
+ #This is a staff_facing_error
+ raise ValueError("Peer grading definition must include at least one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
def parse_task(k):
"""Assumes that xml_object has child k"""
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index 04e7ee19b1..9474717cb2 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -19,6 +19,15 @@ import xmodule
from xmodule.x_module import ModuleSystem
from mock import Mock
+open_ended_grading_interface = {
+ 'url': 'http://sandbox-grader-001.m.edx.org/peer_grading',
+ 'username': 'incorrect_user',
+ 'password': 'incorrect_pass',
+ 'staff_grading' : 'staff_grading',
+ 'peer_grading' : 'peer_grading',
+ 'grading_controller' : 'grading_controller'
+ }
+
test_system = ModuleSystem(
ajax_url='courses/course_id/modx/a_location',
track_function=Mock(),
@@ -31,7 +40,8 @@ test_system = ModuleSystem(
debug=True,
xqueue={'interface': None, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 10},
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
- anonymous_student_id='student'
+ anonymous_student_id='student',
+ open_ended_grading_interface= open_ended_grading_interface
)
diff --git a/common/lib/xmodule/xmodule/tests/test_self_assessment.py b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
index 83f6c0a8bc..b9c3076b7c 100644
--- a/common/lib/xmodule/xmodule/tests/test_self_assessment.py
+++ b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
@@ -1,11 +1,10 @@
import json
-from mock import Mock
+from mock import Mock, MagicMock
import unittest
from xmodule.open_ended_grading_classes.self_assessment_module import SelfAssessmentModule
from xmodule.modulestore import Location
from lxml import etree
-from nose.plugins.skip import SkipTest
from . import test_system
@@ -64,13 +63,29 @@ class SelfAssessmentTest(unittest.TestCase):
self.assertTrue("This is sample prompt text" in html)
def test_self_assessment_flow(self):
- raise SkipTest()
+ responses = {'assessment': '0', 'score_list[]': ['0', '0']}
+ def get_fake_item(name):
+ return responses[name]
+
+ def get_data_for_location(self,location,student):
+ return {
+ 'count_graded' : 0,
+ 'count_required' : 0,
+ 'student_sub_count': 0,
+ }
+
+ mock_query_dict = MagicMock()
+ mock_query_dict.__getitem__.side_effect = get_fake_item
+ mock_query_dict.getlist = get_fake_item
+
+ self.module.peer_gs.get_data_for_location = get_data_for_location
+
self.assertEqual(self.module.get_score()['score'], 0)
self.module.save_answer({'student_answer': "I am an answer"}, test_system)
self.assertEqual(self.module.state, self.module.ASSESSING)
- self.module.save_assessment({'assessment': '0'}, test_system)
+ self.module.save_assessment(mock_query_dict, test_system)
self.assertEqual(self.module.state, self.module.DONE)
@@ -80,5 +95,6 @@ class SelfAssessmentTest(unittest.TestCase):
# if we now assess as right, skip the REQUEST_HINT state
self.module.save_answer({'student_answer': 'answer 4'}, test_system)
- self.module.save_assessment({'assessment': '1'}, test_system)
+ responses['assessment'] = '1'
+ self.module.save_assessment(mock_query_dict, test_system)
self.assertEqual(self.module.state, self.module.DONE)
diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
index ecec29fce3..c4054895d3 100644
--- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py
+++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py
@@ -45,7 +45,8 @@ def staff_grading_notifications(course, user):
except:
#Non catastrophic error, so no real action
notifications = {}
- log.info("Problem with getting notifications from staff grading service.")
+ #This is a dev_facing_error
+ log.info("Problem with getting notifications from staff grading service for course {0} user {1}.".format(course_id, student_id))
if pending_grading:
img_path = "/static/images/grading_notification.png"
@@ -78,7 +79,8 @@ def peer_grading_notifications(course, user):
except:
#Non catastrophic error, so no real action
notifications = {}
- log.info("Problem with getting notifications from peer grading service.")
+ #This is a dev_facing_error
+ log.info("Problem with getting notifications from peer grading service for course {0} user {1}.".format(course_id, student_id))
if pending_grading:
img_path = "/static/images/grading_notification.png"
@@ -123,7 +125,8 @@ def combined_notifications(course, user):
except:
#Non catastrophic error, so no real action
notifications = {}
- log.exception("Problem with getting notifications from controller query service.")
+ #This is a dev_facing_error
+ log.exception("Problem with getting notifications from controller query service for course {0} user {1}.".format(course_id, student_id))
if pending_grading:
img_path = "/static/images/grading_notification.png"
diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py
index 0ead7aa364..79b92dffba 100644
--- a/lms/djangoapps/open_ended_grading/staff_grading_service.py
+++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py
@@ -18,6 +18,7 @@ from mitxmako.shortcuts import render_to_string
log = logging.getLogger(__name__)
+STAFF_ERROR_MESSAGE = 'Could not contact the external grading server. Please contact the development team. If you do not have a point of contact, you can contact Vik at vik@edx.org.'
class MockStaffGradingService(object):
"""
@@ -254,10 +255,12 @@ def get_problem_list(request, course_id):
return HttpResponse(response,
mimetype="application/json")
except GradingServiceError:
- log.exception("Error from grading service. server url: {0}"
+ #This is a dev_facing_error
+ log.exception("Error from staff grading service in open ended grading. server url: {0}"
.format(staff_grading_service().url))
+ #This is a staff_facing_error
return HttpResponse(json.dumps({'success': False,
- 'error': 'Could not connect to grading service'}))
+ 'error': STAFF_ERROR_MESSAGE}))
def _get_next(course_id, grader_id, location):
@@ -267,10 +270,12 @@ def _get_next(course_id, grader_id, location):
try:
return staff_grading_service().get_next(course_id, location, grader_id)
except GradingServiceError:
- log.exception("Error from grading service. server url: {0}"
+ #This is a dev facing error
+ log.exception("Error from staff grading service in open ended grading. server url: {0}"
.format(staff_grading_service().url))
+ #This is a staff_facing_error
return json.dumps({'success': False,
- 'error': 'Could not connect to grading service'})
+ 'error': STAFF_ERROR_MESSAGE})
@expect_json
@@ -316,18 +321,23 @@ def save_grade(request, course_id):
p.getlist('rubric_scores[]'),
p['submission_flagged'])
except GradingServiceError:
- log.exception("Error saving grade")
- return _err_response('Could not connect to grading service')
+ #This is a dev_facing_error
+ log.exception("Error saving grade in the staff grading interface in open ended grading. Request: {0} Course ID: {1}".format(request, course_id))
+ #This is a staff_facing_error
+ return _err_response(STAFF_ERROR_MESSAGE)
try:
result = json.loads(result_json)
except ValueError:
- log.exception("save_grade returned broken json: %s", result_json)
- return _err_response('Grading service returned mal-formatted data.')
+ #This is a dev_facing_error
+ log.exception("save_grade returned broken json in the staff grading interface in open ended grading: {0}".format(result_json))
+ #This is a staff_facing_error
+ return _err_response(STAFF_ERROR_MESSAGE)
if not result.get('success', False):
- log.warning('Got success=False from grading service. Response: %s', result_json)
- return _err_response('Grading service failed')
+ #This is a dev_facing_error
+ log.warning('Got success=False from staff grading service in open ended grading. Response: {0}'.format(result_json))
+ return _err_response(STAFF_ERROR_MESSAGE)
# Ok, save_grade seemed to work. Get the next submission to grade.
return HttpResponse(_get_next(course_id, grader_id, location),
diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py
index 63048c78d4..d452883ebb 100644
--- a/lms/djangoapps/open_ended_grading/tests.py
+++ b/lms/djangoapps/open_ended_grading/tests.py
@@ -16,7 +16,7 @@ import courseware.tests.tests as ct
from xmodule.modulestore.django import modulestore
import xmodule.modulestore.django
from nose import SkipTest
-from mock import patch, Mock
+from mock import patch, Mock, MagicMock
import json
from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string
@@ -169,23 +169,35 @@ class TestPeerGradingService(ct.PageLoader):
def test_get_next_submission_missing_location(self):
data = {}
- r = self.peer_module.get_next_submission(data)
- d = r
+ d = self.peer_module.get_next_submission(data)
self.assertFalse(d['success'])
self.assertEqual(d['error'], "Missing required keys: location")
def test_save_grade_success(self):
- raise SkipTest()
- data = 'rubric_scores[]=1|rubric_scores[]=2|location=' + self.location + '|submission_id=1|submission_key=fake key|score=2|feedback=feedback|submission_flagged=False'
- qdict = QueryDict(data.replace("|", "&"))
+ data = {
+ 'rubric_scores[]': [0, 0],
+ 'location': self.location,
+ 'submission_id': 1,
+ 'submission_key': 'fake key',
+ 'score': 2,
+ 'feedback': 'feedback',
+ 'submission_flagged': 'false'
+ }
+
+ qdict = MagicMock()
+ def fake_get_item(key):
+ return data[key]
+ qdict.__getitem__.side_effect = fake_get_item
+ qdict.getlist = fake_get_item
+ qdict.keys = data.keys
+
r = self.peer_module.save_grade(qdict)
- d = r
+ d = json.loads(r)
self.assertTrue(d['success'])
def test_save_grade_missing_keys(self):
data = {}
- r = self.peer_module.save_grade(data)
- d = r
+ d = self.peer_module.save_grade(data)
self.assertFalse(d['success'])
self.assertTrue(d['error'].find('Missing required keys:') > -1)
@@ -198,8 +210,7 @@ class TestPeerGradingService(ct.PageLoader):
def test_is_calibrated_failure(self):
data = {}
- r = self.peer_module.is_student_calibrated(data)
- d = r
+ d = self.peer_module.is_student_calibrated(data)
self.assertFalse(d['success'])
self.assertFalse('calibrated' in d)
@@ -219,25 +230,36 @@ class TestPeerGradingService(ct.PageLoader):
def test_show_calibration_essay_missing_key(self):
data = {}
- r = self.peer_module.show_calibration_essay(data)
- d = r
+ d = self.peer_module.show_calibration_essay(data)
self.assertFalse(d['success'])
self.assertEqual(d['error'], "Missing required keys: location")
def test_save_calibration_essay_success(self):
- raise SkipTest()
- data = 'rubric_scores[]=1|rubric_scores[]=2|location=' + self.location + '|submission_id=1|submission_key=fake key|score=2|feedback=feedback|submission_flagged=False'
- qdict = QueryDict(data.replace("|", "&"))
- r = self.peer_module.save_calibration_essay(qdict)
- d = r
+ data = {
+ 'rubric_scores[]': [0, 0],
+ 'location': self.location,
+ 'submission_id': 1,
+ 'submission_key': 'fake key',
+ 'score': 2,
+ 'feedback': 'feedback',
+ 'submission_flagged': 'false'
+ }
+
+ qdict = MagicMock()
+ def fake_get_item(key):
+ return data[key]
+ qdict.__getitem__.side_effect = fake_get_item
+ qdict.getlist = fake_get_item
+ qdict.keys = data.keys
+
+ d = self.peer_module.save_calibration_essay(qdict)
self.assertTrue(d['success'])
self.assertTrue('actual_score' in d)
def test_save_calibration_essay_missing_keys(self):
data = {}
- r = self.peer_module.save_calibration_essay(data)
- d = r
+ d = self.peer_module.save_calibration_essay(data)
self.assertFalse(d['success'])
self.assertTrue(d['error'].find('Missing required keys:') > -1)
self.assertFalse('actual_score' in d)
diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py
index 80f50b282d..77c1cda6bc 100644
--- a/lms/djangoapps/open_ended_grading/views.py
+++ b/lms/djangoapps/open_ended_grading/views.py
@@ -60,6 +60,8 @@ ALERT_DICT = {
'Flagged Submissions': "Submissions have been flagged for review"
}
+STUDENT_ERROR_MESSAGE = "Error occured while contacting the grading service. Please notify course staff."
+STAFF_ERROR_MESSAGE = "Error occured while contacting the grading service. Please notify the development team. If you do not have a point of contact, please email Vik at vik@edx.org"
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def staff_grading(request, course_id):
@@ -96,7 +98,9 @@ def peer_grading(request, course_id):
return HttpResponseRedirect(problem_url)
except:
+ #This is a student_facing_error
error_message = "Error with initializing peer grading. Centralized module does not exist. Please contact course staff."
+ #This is a dev_facing_error
log.exception(error_message + "Current course is: {0}".format(course_id))
return HttpResponse(error_message)
@@ -132,30 +136,34 @@ def student_problem_list(request, course_id):
problem_list = []
base_course_url = reverse('courses')
- #try:
- problem_list_json = controller_qs.get_grading_status_list(course_id, unique_id_for_user(request.user))
- problem_list_dict = json.loads(problem_list_json)
- success = problem_list_dict['success']
- if 'error' in problem_list_dict:
- error_text = problem_list_dict['error']
- problem_list = []
- else:
- problem_list = problem_list_dict['problem_list']
+ try:
+ problem_list_json = controller_qs.get_grading_status_list(course_id, unique_id_for_user(request.user))
+ problem_list_dict = json.loads(problem_list_json)
+ success = problem_list_dict['success']
+ if 'error' in problem_list_dict:
+ error_text = problem_list_dict['error']
+ problem_list = []
+ else:
+ problem_list = problem_list_dict['problem_list']
- for i in xrange(0, len(problem_list)):
- problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location'])
- problem_url = generate_problem_url(problem_url_parts, base_course_url)
- problem_list[i].update({'actual_url': problem_url})
+ for i in xrange(0, len(problem_list)):
+ problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location'])
+ problem_url = generate_problem_url(problem_url_parts, base_course_url)
+ problem_list[i].update({'actual_url': problem_url})
- """
except GradingServiceError:
- error_text = "Error occured while contacting the grading service"
+ #This is a student_facing_error
+ error_text = STUDENT_ERROR_MESSAGE
+ #This is a dev facing error
+ log.error("Problem contacting open ended grading service.")
success = False
# catch error if if the json loads fails
except ValueError:
- error_text = "Could not get problem list"
+ #This is a student facing error
+ error_text = STUDENT_ERROR_MESSAGE
+ #This is a dev_facing_error
+ log.error("Problem with results from external grading service for open ended.")
success = False
- """
ajax_url = _reverse_with_slash('open_ended_problems', course_id)
@@ -195,11 +203,17 @@ def flagged_problem_list(request, course_id):
problem_list = problem_list_dict['flagged_submissions']
except GradingServiceError:
- error_text = "Error occured while contacting the grading service"
+ #This is a staff_facing_error
+ error_text = STAFF_ERROR_MESSAGE
+ #This is a dev_facing_error
+ log.error("Could not get flagged problem list from external grading service for open ended.")
success = False
# catch error if if the json loads fails
except ValueError:
- error_text = "Could not get problem list"
+ #This is a staff_facing_error
+ error_text = STAFF_ERROR_MESSAGE
+ #This is a dev_facing_error
+ log.error("Could not parse problem list from external grading service response.")
success = False
ajax_url = _reverse_with_slash('open_ended_flagged_problems', course_id)
@@ -283,7 +297,8 @@ def take_action_on_flags(request, course_id):
required = ['submission_id', 'action_type', 'student_id']
for key in required:
if key not in request.POST:
- return HttpResponse(json.dumps({'success': False, 'error': 'Missing key {0}'.format(key)}),
+ #This is a staff_facing_error
+ return HttpResponse(json.dumps({'success': False, 'error': STAFF_ERROR_MESSAGE + 'Missing key {0} from submission. Please reload and try again.'.format(key)}),
mimetype="application/json")
p = request.POST
@@ -297,5 +312,6 @@ def take_action_on_flags(request, course_id):
response = controller_qs.take_action_on_flags(course_id, student_id, submission_id, action_type)
return HttpResponse(response, mimetype="application/json")
except GradingServiceError:
- log.exception("Error saving calibration grade, submission_id: {0}, submission_key: {1}, grader_id: {2}".format(submission_id, submission_key, grader_id))
- return _err_response('Could not connect to grading service')
+ #This is a dev_facing_error
+ log.exception("Error taking action on flagged peer grading submissions, submission_id: {0}, action_type: {1}, grader_id: {2}".format(submission_id, action_type, grader_id))
+ return _err_response(STAFF_ERROR_MESSAGE)
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 6cad6416d0..c1863349fb 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -170,4 +170,4 @@ PASSWORD_HASHERS = (
'django.contrib.auth.hashers.SHA1PasswordHasher',
'django.contrib.auth.hashers.MD5PasswordHasher',
# 'django.contrib.auth.hashers.CryptPasswordHasher',
-)
+)
\ No newline at end of file
diff --git a/lms/static/coffee/src/open_ended/open_ended.coffee b/lms/static/coffee/src/open_ended/open_ended.coffee
index cc8bad5473..bfb0fa5931 100644
--- a/lms/static/coffee/src/open_ended/open_ended.coffee
+++ b/lms/static/coffee/src/open_ended/open_ended.coffee
@@ -41,7 +41,7 @@ class OpenEnded
post: (cmd, data, callback) ->
# if this post request fails, the error callback will catch it
$.post(@ajax_url + cmd, data, callback)
- .error => callback({success: false, error: "Error occured while performing this operation"})
+ .error => callback({success: false, error: "Error occured while performing javascript ajax post."})
after_action_wrapper: (target, action_type) ->
tr_parent = target.parent().parent()
diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee
index 4edbce9ddb..f2b1b387ca 100644
--- a/lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -143,7 +143,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
else
# TODO: replace with postWithPrefix when that's loaded
$.post(@ajax_url + cmd, data, callback)
- .error => callback({success: false, error: "Error occured while performing this operation"})
+ .error => callback({success: false, error: "Error occured while performing javascript AJAX post."})
class @StaffGrading
diff --git a/lms/static/images/university/anu/anu-cover.jpg b/lms/static/images/university/anu/anu-cover.jpg
new file mode 100644
index 0000000000..591df9791d
Binary files /dev/null and b/lms/static/images/university/anu/anu-cover.jpg differ
diff --git a/lms/static/images/university/anu/anu.png b/lms/static/images/university/anu/anu.png
new file mode 100644
index 0000000000..5caeb0b180
Binary files /dev/null and b/lms/static/images/university/anu/anu.png differ
diff --git a/lms/static/images/university/delft/delft-cover.jpg b/lms/static/images/university/delft/delft-cover.jpg
new file mode 100644
index 0000000000..73092d7596
Binary files /dev/null and b/lms/static/images/university/delft/delft-cover.jpg differ
diff --git a/lms/static/images/university/delft/delft.png b/lms/static/images/university/delft/delft.png
new file mode 100644
index 0000000000..03c566e91f
Binary files /dev/null and b/lms/static/images/university/delft/delft.png differ
diff --git a/lms/static/images/university/epfl/epfl-cover.jpg b/lms/static/images/university/epfl/epfl-cover.jpg
new file mode 100644
index 0000000000..42b188c925
Binary files /dev/null and b/lms/static/images/university/epfl/epfl-cover.jpg differ
diff --git a/lms/static/images/university/epfl/epfl.png b/lms/static/images/university/epfl/epfl.png
new file mode 100644
index 0000000000..6725340a1a
Binary files /dev/null and b/lms/static/images/university/epfl/epfl.png differ
diff --git a/lms/static/images/university/mcgill/mcgill-cover.jpg b/lms/static/images/university/mcgill/mcgill-cover.jpg
new file mode 100644
index 0000000000..4d213c8c92
Binary files /dev/null and b/lms/static/images/university/mcgill/mcgill-cover.jpg differ
diff --git a/lms/static/images/university/mcgill/mcgill.png b/lms/static/images/university/mcgill/mcgill.png
new file mode 100644
index 0000000000..5a0bcb56ad
Binary files /dev/null and b/lms/static/images/university/mcgill/mcgill.png differ
diff --git a/lms/static/images/university/rice/rice-cover.jpg b/lms/static/images/university/rice/rice-cover.jpg
new file mode 100644
index 0000000000..2266a72c6d
Binary files /dev/null and b/lms/static/images/university/rice/rice-cover.jpg differ
diff --git a/lms/static/images/university/rice/rice.png b/lms/static/images/university/rice/rice.png
new file mode 100644
index 0000000000..95865f2dba
Binary files /dev/null and b/lms/static/images/university/rice/rice.png differ
diff --git a/lms/static/images/university/toronto/toronto-cover.jpg b/lms/static/images/university/toronto/toronto-cover.jpg
new file mode 100644
index 0000000000..0d3434659e
Binary files /dev/null and b/lms/static/images/university/toronto/toronto-cover.jpg differ
diff --git a/lms/static/images/university/toronto/toronto.png b/lms/static/images/university/toronto/toronto.png
new file mode 100644
index 0000000000..86851b9468
Binary files /dev/null and b/lms/static/images/university/toronto/toronto.png differ
diff --git a/lms/static/sass/multicourse/_courses.scss b/lms/static/sass/multicourse/_courses.scss
index 7e5bd4ff38..45ecfcd23f 100644
--- a/lms/static/sass/multicourse/_courses.scss
+++ b/lms/static/sass/multicourse/_courses.scss
@@ -5,6 +5,7 @@
header.search {
background: rgb(240,240,240);
background-size: cover;
+ background-position: center top !important;
border-bottom: 1px solid rgb(100,100,100);
@include box-shadow(inset 0 -1px 8px 0 rgba(0,0,0, 0.2), inset 0 1px 12px 0 rgba(0,0,0, 0.3));
height: 430px;
diff --git a/lms/static/sass/multicourse/_home.scss b/lms/static/sass/multicourse/_home.scss
index 669bd889b0..b5546aa470 100644
--- a/lms/static/sass/multicourse/_home.scss
+++ b/lms/static/sass/multicourse/_home.scss
@@ -1,6 +1,11 @@
.home {
padding: 0px;
+ > .container {
+ @include box-sizing(border-box);
+ width: flex-grid(12);
+ }
+
> header {
background: rgb(255,255,255);
@include background-image(url('/static/images/homepage-bg.jpg'));
@@ -175,9 +180,6 @@
}
.university-partners {
- @include background-image(linear-gradient(180deg, rgba(245,245,245, 0) 0%,
- rgba(245,245,245, 1) 50%,
- rgba(245,245,245, 0) 100%));
border-bottom: 1px solid rgb(210,210,210);
margin-bottom: 0px;
overflow: hidden;
@@ -300,7 +302,6 @@
}
img {
- max-width: 190px;
position: relative;
@include transition(all, 0.25s, ease-in-out);
vertical-align: middle;
@@ -324,6 +325,44 @@
}
}
}
+
+ &.university-partners2x6 {
+ @include box-sizing(border-box);
+ width: flex-grid(12, 12);
+
+ .partners {
+ @include box-sizing(border-box);
+ @include clearfix();
+ margin-left: 60px;
+ padding: 12px 0;
+
+ .partner {
+ @include box-sizing(border-box);
+ width: flex-grid(2, 12);
+ display: block;
+ float: left;
+ padding: 0 12px;
+
+ a {
+
+ img {
+ width: 100%;
+ height: auto;
+ }
+
+ .name > span {
+ font-size: 1.0em;
+ }
+
+ &:hover {
+ .name {
+ bottom: 14px;
+ }
+ }
+ }
+ }
+ }
+ }
}
.more-info {
diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html
index fa84e7bab7..d13077737f 100644
--- a/lms/templates/combined_open_ended_status.html
+++ b/lms/templates/combined_open_ended_status.html
@@ -16,7 +16,7 @@
%else:
${status['human_task']}
%endif
- (${status['human_state']}) ${status['score']} / ${status['max_score']}
+ (${status['human_state']})
%endfor
diff --git a/lms/templates/feed.rss b/lms/templates/feed.rss
index 8048c86cbd..a5b40ca11c 100644
--- a/lms/templates/feed.rss
+++ b/lms/templates/feed.rss
@@ -6,7 +6,16 @@
##
EdX Blog
- 2013-01-30T14:00:12-07:00
+ 2013-02-20T14:00:12-07:00
+
+ tag:www.edx.org,2012:Post/13
+ 2013-02-20T10:00:00-07:00
+ 2013-02-20T10:00:00-07:00
+
+ edX Expands Internationally and Doubles its Institutional Membership with the Addition of Six New Schools
+ <img src="${static.url('images/press/releases/edx-logo_240x180.png')}" />
+ <p></p>
+ tag:www.edx.org,2012:Post/132013-01-30T10:00:00-07:00
@@ -16,15 +25,6 @@
<img src="${static.url('images/press/releases/eric-lander_240x180.jpg')}" />
<p></p>
-
- tag:www.edx.org,2012:Post/12
- 2013-01-29T10:00:00-07:00
- 2013-01-29T10:00:00-07:00
-
- City of Boston and edX partner to establish BostonX to improve educational access for residents
- <img src="${static.url('images/press/releases/edx-logo_240x180.png')}" />
- <p></p>
- tag:www.edx.org,2012:Post/112013-01-22T10:00:00-07:00
@@ -34,6 +34,15 @@
<img src="${static.url('images/press/releases/dr-lewin-316_240x180.jpg')}" />
<p></p>
+
+ tag:www.edx.org,2012:Post/12
+ 2013-01-29T10:00:00-07:00
+ 2013-01-29T10:00:00-07:00
+
+ City of Boston and edX partner to establish BostonX to improve educational access for residents
+ <img src="${static.url('images/press/releases/edx-logo_240x180.png')}" />
+ <p></p>
+
diff --git a/lms/templates/index.html b/lms/templates/index.html
index d08ba09e61..3e6d22cc91 100644
--- a/lms/templates/index.html
+++ b/lms/templates/index.html
@@ -47,8 +47,8 @@
Flag this submission for review by course staff (use if the submission contains inappropriate content):
+
I do not know how to grade this question:
diff --git a/lms/templates/static_templates/press_releases/edx_expands_internationally.html b/lms/templates/static_templates/press_releases/edx_expands_internationally.html
new file mode 100644
index 0000000000..0ee42dafa9
--- /dev/null
+++ b/lms/templates/static_templates/press_releases/edx_expands_internationally.html
@@ -0,0 +1,81 @@
+<%! from django.core.urlresolvers import reverse %>
+<%inherit file="../../main.html" />
+
+<%namespace name='static' file='../../static_content.html'/>
+
+<%block name="title">edX Expands Internationally and Doubles its Institutional Membership with the Addition of Six New Schools%block>
+
+
+
+
+
+
edX Expands Internationally and Doubles its Institutional Membership with the Addition of Six New Schools
+
+
+
edX welcomes The Australian National University, Delft University of Technology, École Polytechnique Fédérale de Lausanne, McGill University, Rice University and University of Toronto to its X University Consortium of the world’s leading higher education institutions
+
+
CAMBRIDGE, MA – Feb. 20, 2013 –
+EdX, the not-for-profit online learning enterprise founded by Harvard University and the Massachusetts Institute of Technology (MIT), announced today the international expansion of its X University Consortium with the addition of six new global higher education institutions. The Australian National University (ANU), Delft University of Technology in the Netherlands, École Polytechnique Fédérale de Lausanne (EPFL) in Switzerland, McGill University and the University of Toronto in Canada, and Rice University in the United States are joining the Consortium and will use the edX platform to deliver the next generation of online and blended courses. This international expansion enables edX to better achieve its mission of providing world-class courses to everyone, everywhere, and is the natural next step to continue serving the large international student body already using edX on a daily basis.
+
+
+
While MOOCs, or massive open online courses, have typically focused on offering a variety of online courses inexpensively or for free, edX's vision is much larger. EdX is building an open source educational platform and a network of the world's top universities to improve education both online and on campus while conducting research on how students learn. To date, edX has more than 700,000 individuals on its platform, who account for more than 900,000 course enrollments. The addition of these new higher education institutions stretching from North America to Europe to the Asia Pacific will double the number of X University Consortium members and add a rich variety of new courses to edX’s offerings:
+
+
+
+
The Australian National University, a celebrated place of intensive research, education and policy engagement, will provide a series of ANUx courses to the open source platform including Astrophysics taught by Nobel Laureate and Professor of Astrophysics Brian Schmidt and his colleague Dr. Paul Francis, and Engaging India, taught by Dr. McComas Taylor and Dr. Peter Friedlander.
+
+
Delft University of Technology, the largest and oldest technological university in the Netherlands, will provide a series of DelftX courses under Creative Commons license, including Introduction to Aerospace Engineering by Professor Jacco Hoekstra, Solar Energy by Dr. Arno Smets, and Water Treatment Engineering by Professor Jules van Lier.
+
+
École Polytechnique Fédérale de Lausanne, one of the most famous institutions of science and technology in Europe, will provide a series of EPFLx courses specially tailored to fit the edX format, originating from its five schools -- Engineering, Life Sciences, Informatics and Communication, Architecture and Basic Sciences.
+
+
McGill University, one of Canada's best-known institutions of higher learning and one of the leading universities in the world, will provide a series of McGillX courses in areas ranging from science and the humanities to public policy issues.
+
+
Rice University, in Houston, Texas, is consistently ranked among the nation's top 20 universities by U.S. News & World Report. Rice has highly respected schools of Architecture, Business, Continuing Studies, Engineering, Humanities, Music, Natural Sciences and Social Sciences and is home to the Baker Institute for Public Policy. Rice's Smalley Institute for Nanoscale Science and Technology was the world’s first nanotechnology center when it opened in 1991. Rice will initially provide four RiceX courses and investigate ways to integrate its learning analytics tools from OpenStax Tutor to enable students and instructors to track their progress in real time.
+
+
University of Toronto, one of the most respected and influential institutions of higher education and advanced research in the world, will provide a series of TorontoX courses including Terrestrial Energy System by Professor Bryan Kanrey, Behavioral Economics by Professor Dilip Soman, The Logic of Business: Building Blocks for Organizational Design by Professor Mihnea Moldoveanu, and Bioinformatic Methods by Professor Nicholas Provart.
+
+
+
“We have had an international student community from the very beginning, and bringing these leading universities, from North America and Europe and the Asia Pacific into the edX organization will help us meet the tremendous demand we are experiencing,” said Anant Agarwal, President of edX. “Each of these schools was carefully selected for the distinct expertise they bring to our growing family of edX institutions. We remain committed to growing edX to meet the needs of the world while maintaining a superior learning experience for all.”
+
+
Courses offered by institutions on the edX platform provide the same rigor as on-campus classes but are designed to take advantage of the unique features and benefits of online learning environments, including game-like experiences, instant feedback and cutting-edge virtual laboratories. Through edX, the new X Universities will provide interactive education experiences for students around the world. All that is required of edX students is access to the Internet and a desire to learn. By breaking down the barriers of location and cost and enabling the global exchange of information and ideas, edX is changing the foundations of both teaching and learning.
+
+
The new member institutions will join founding universities MIT and Harvard, as well as the University of California, Berkeley, the University of Texas System, Wellesley College and Georgetown University in the X University Consortium. ANUx, DelftX, EPFLx, McGillX, RiceX and TorontoX will offer courses on edX beginning in late 2013. All of the courses will be hosted on edX’s open source platform at www.edx.org.
+
+
+
About edX
+
+
EdX is a not-for-profit enterprise of its founding partners Harvard University and the Massachusetts Institute of Technology focused on transforming online and on-campus learning through groundbreaking methodologies, game-like experiences and cutting-edge research. EdX provides inspirational and transformative knowledge to students of all ages, social status, and income who form worldwide communities of learners. EdX uses its open source technology to transcend physical and social borders. We’re focused on people, not profit. EdX is based in Cambridge, Massachusetts in the USA.
The Australian National University (ANU) is a celebrated place of intensive research, education and policy engagement. Our research has always been central to everything we do, shaping a holistic learning experience that goes beyond the classroom, giving students access to researchers who are among the best in their fields and to opportunities for development around Australia and the world.
Delft University of Technology is the largest and oldest technological university in the Netherlands. Our research is inspired by the desire to increase fundamental understanding, as well as by societal challenges. We encourage our students to be independent thinkers so they will become engineers capable of solving complex problems. Our students have chosen Delft University of Technology because of our reputation for quality education and research.
EPFL is one of the two Swiss Federal Institutes of Technology. With the status of a national school since 1969, the young engineering school has grown in many dimensions, to the extent of becoming one of the most famous European institutions of science and technology. It has three core missions: training, research and technology transfer.
+
+
EPFL is located in Lausanne in Switzerland, on the shores of the largest lake in Europe, Lake Geneva and at the foot of the Alps and Mont-Blanc. Its main campus brings together over 11,000 persons, students, researchers and staff in the same magical place. Because of its dynamism and rich student community, EPFL has been able to create a special spirit imbued with curiosity and simplicity. Daily interactions amongst students, researchers and entrepreneurs on campus give rise to new scientific, technological and architectural projects.
+
McGill University is one of Canada's best-known institutions of higher learning and one of the leading universities in the world. McGill is located in vibrant multicultural Montreal, in the province of Quebec. Our 11 faculties and 11 professional schools offer more than 300 programs to some 38,000 graduate, undergraduate and continuing studies students. McGill ranks 1st in Canada among medical-doctoral universities (Maclean’s) and 18th in the world (QS World University Rankings).
Located on a 300-acre forested campus in Houston, Rice University is consistently ranked among the nation's top 20 universities by U.S. News & World Report. Rice has highly respected schools of Architecture, Business, Continuing Studies, Engineering, Humanities, Music, Natural Sciences and Social Sciences and is home to the Baker Institute for Public Policy. With 3,708 undergraduates and 2,374 graduate students, Rice's undergraduate student-to-faculty ratio is 6-to-1. Its residential college system builds close-knit communities and lifelong friendships, just one reason why Rice has been ranked No. 1 for best quality of life multiple times by the Princeton Review and No. 2 for "best value" among private universities by Kiplinger's Personal Finance.
Established in 1827, the University of Toronto is a vibrant and diverse academic community. It includes 80,000 students, 12,000 colleagues holding faculty appointments, 200 librarians, and 6,000 staff members across three distinctive campuses and at many partner sites, including world-renowned hospitals. With over 800 undergraduate programs, 150 graduate programs, and 40 professional programs, U of T attracts students of the highest calibre, from across Canada and from 160 countries around the world. The University is one of the most respected and influential institutions of higher education and advanced research in the world. Its strengths extend across the full range of disciplines: the 2012-13 Times Higher Education ranking groups the University of Toronto with Stanford, UC Berkeley, UCLA, Columbia, Cambridge, Oxford, the University of Melbourne, and the University of Michigan as the only institutions in the top 27 in all 6 broad disciplinary areas. The University is also consistently rated one of Canada’s Top 100 employers, and ranks with Harvard and Yale for the top university library resources in North America.
+%block>
+
+${parent.body()}
diff --git a/lms/urls.py b/lms/urls.py
index fc42577085..cc6fabd9cb 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -2,6 +2,7 @@ from django.conf import settings
from django.conf.urls import patterns, include, url
from django.contrib import admin
from django.conf.urls.static import static
+from django.views.generic import RedirectView
import django.contrib.auth.views
# Uncomment the next two lines to enable the admin:
@@ -65,10 +66,47 @@ urlpatterns = ('',
url(r'^heartbeat$', include('heartbeat.urls')),
- url(r'^university_profile/UTx$', 'courseware.views.static_university_profile', name="static_university_profile", kwargs={'org_id': 'UTx'}),
- url(r'^university_profile/WellesleyX$', 'courseware.views.static_university_profile', name="static_university_profile", kwargs={'org_id': 'WellesleyX'}),
- url(r'^university_profile/GeorgetownX$', 'courseware.views.static_university_profile', name="static_university_profile", kwargs={'org_id': 'GeorgetownX'}),
- url(r'^university_profile/(?P[^/]+)$', 'courseware.views.university_profile', name="university_profile"),
+ url(r'^university_profile/UTx$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'UTx'}),
+ url(r'^university_profile/WellesleyX$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'WellesleyX'}),
+ url(r'^university_profile/GeorgetownX$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'GeorgetownX'}),
+
+ # Dan accidentally sent out a press release with lower case urls for McGill, Toronto,
+ # Rice, ANU, Delft, and EPFL. Hence the redirects.
+ url(r'^university_profile/McGillX$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'McGillX'}),
+ url(r'^university_profile/mcgillx$',
+ RedirectView.as_view(url='/university_profile/McGillX')),
+
+ url(r'^university_profile/TorontoX$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'TorontoX'}),
+ url(r'^university_profile/torontox$',
+ RedirectView.as_view(url='/university_profile/TorontoX')),
+
+ url(r'^university_profile/RiceX$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'RiceX'}),
+ url(r'^university_profile/ricex$',
+ RedirectView.as_view(url='/university_profile/RiceX')),
+
+ url(r'^university_profile/ANUx$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'ANUx'}),
+ url(r'^university_profile/anux$',
+ RedirectView.as_view(url='/university_profile/ANUx')),
+
+ url(r'^university_profile/DelftX$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'DelftX'}),
+ url(r'^university_profile/delftx$',
+ RedirectView.as_view(url='/university_profile/DelftX')),
+
+ url(r'^university_profile/EPFLx$', 'courseware.views.static_university_profile',
+ name="static_university_profile", kwargs={'org_id': 'EPFLx'}),
+ url(r'^university_profile/epflx$',
+ RedirectView.as_view(url='/university_profile/EPFLx')),
+
+ url(r'^university_profile/(?P[^/]+)$', 'courseware.views.university_profile',
+ name="university_profile"),
#Semi-static views (these need to be rendered and have the login bar, but don't change)
url(r'^404$', 'static_template_view.views.render',
@@ -128,11 +166,14 @@ urlpatterns = ('',
url(r'^press/eric-lander-secret-of-life$', 'static_template_view.views.render',
{'template': 'press_releases/eric_lander_secret_of_life.html'},
name="press/eric-lander-secret-of-life"),
+ url(r'^press/edx-expands-internationally$', 'static_template_view.views.render',
+ {'template': 'press_releases/edx_expands_internationally.html'},
+ name="press/edx-expands-internationally"),
# Should this always update to point to the latest press release?
(r'^pressrelease$', 'django.views.generic.simple.redirect_to',
- {'url': '/press/eric-lander-secret-of-life'}),
+ {'url': '/press/edx-expands-internationally'}),
(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}),