Commit 3a8b7156 by Diana Huang

Merge branch 'master' into diana/click-tracking

parents 18a56005 8b1579e4
......@@ -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%;
}
}
}
}
......
......@@ -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)
......
......@@ -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
......
......@@ -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')
......
......@@ -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"""
......
......@@ -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 <category> tag: got {0} instead".format(category.tag))
#This is a staff_facing_error
raise RubricParsingError("[extract_categories] Expected a <category> 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 = []
......
......@@ -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"}
......@@ -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
......
......@@ -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"""
......
......@@ -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
......@@ -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):
......
......@@ -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"""
......
......@@ -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"""
......
......@@ -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
)
......
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)
......@@ -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"
......
......@@ -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),
......
......@@ -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)
......@@ -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']
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})
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})
"""
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)
......@@ -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
......@@ -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()
......
......@@ -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
......
......@@ -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;
......
.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 {
......
......@@ -16,7 +16,7 @@
%else:
${status['human_task']}
%endif
(${status['human_state']}) ${status['score']} / ${status['max_score']}
(${status['human_state']})
</div>
%endfor
</section>
......
......@@ -6,7 +6,16 @@
<link type="text/html" rel="alternate" href="http://blog.edx.org/"/>
##<link type="application/atom+xml" rel="self" href="https://github.com/blog.atom"/>
<title>EdX Blog</title>
<updated>2013-01-30T14:00:12-07:00</updated>
<updated>2013-02-20T14:00:12-07:00</updated>
<entry>
<id>tag:www.edx.org,2012:Post/13</id>
<published>2013-02-20T10:00:00-07:00</published>
<updated>2013-02-20T10:00:00-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/edx-expands-internationally')}"/>
<title>edX Expands Internationally and Doubles its Institutional Membership with the Addition of Six New Schools</title>
<content type="html">&lt;img src=&quot;${static.url('images/press/releases/edx-logo_240x180.png')}&quot; /&gt;
&lt;p&gt;&lt;/p&gt;</content>
</entry>
<entry>
<id>tag:www.edx.org,2012:Post/13</id>
<published>2013-01-30T10:00:00-07:00</published>
......@@ -17,15 +26,6 @@
&lt;p&gt;&lt;/p&gt;</content>
</entry>
<entry>
<id>tag:www.edx.org,2012:Post/12</id>
<published>2013-01-29T10:00:00-07:00</published>
<updated>2013-01-29T10:00:00-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/bostonx-announcement')}"/>
<title>City of Boston and edX partner to establish BostonX to improve educational access for residents</title>
<content type="html">&lt;img src=&quot;${static.url('images/press/releases/edx-logo_240x180.png')}&quot; /&gt;
&lt;p&gt;&lt;/p&gt;</content>
</entry>
<entry>
<id>tag:www.edx.org,2012:Post/11</id>
<published>2013-01-22T10:00:00-07:00</published>
<updated>2013-01-22T10:00:00-07:00</updated>
......@@ -34,6 +34,15 @@
<content type="html">&lt;img src=&quot;${static.url('images/press/releases/dr-lewin-316_240x180.jpg')}&quot; /&gt;
&lt;p&gt;&lt;/p&gt;</content>
</entry>
<entry>
<id>tag:www.edx.org,2012:Post/12</id>
<published>2013-01-29T10:00:00-07:00</published>
<updated>2013-01-29T10:00:00-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/bostonx-announcement')}"/>
<title>City of Boston and edX partner to establish BostonX to improve educational access for residents</title>
<content type="html">&lt;img src=&quot;${static.url('images/press/releases/edx-logo_240x180.png')}&quot; /&gt;
&lt;p&gt;&lt;/p&gt;</content>
</entry>
<!-- <entry> -->
<!-- <id>tag:www.edx.org,2012:Post/10</id> -->
<!-- <published>2012-12-19T14:00:00-07:00</published> -->
......
......@@ -47,8 +47,8 @@
<section class="highlighted-courses">
<h2>Explore free courses from <span class="edx">edX</span> universities</h2>
<section class="university-partners">
<ol class="partners partners-primary">
<section class="university-partners university-partners2x6">
<ol class="partners">
<li class="partner mit">
<a href="${reverse('university_profile', args=['MITx'])}">
<img src="${static.url('images/university/mit/mit.png')}" />
......@@ -65,7 +65,7 @@
</div>
</a>
</li>
<li class="partner last">
<li class="partner">
<a href="${reverse('university_profile', args=['BerkeleyX'])}">
<img src="${static.url('images/university/berkeley/berkeley.png')}" />
<div class="name">
......@@ -73,11 +73,6 @@
</div>
</a>
</li>
</ol>
<hr />
<ol class="partners">
<li class="partner">
<a href="${reverse('university_profile', args=['UTx'])}">
<img src="${static.url('images/university/ut/ut-rollover_350x150.png')}" />
......@@ -87,6 +82,27 @@
</a>
</li>
<li class="partner">
<a href="${reverse('university_profile', args=['McGillX'])}">
<img src="${static.url('images/university/mcgill/mcgill.png')}" />
<div class="name">
<span>McGillX</span>
</div>
</a>
</li>
<li class="partner">
<a href="${reverse('university_profile', args=['ANUx'])}">
<img src="${static.url('images/university/anu/anu.png')}" />
<div class="name">
<span>ANUx</span>
</div>
</a>
</li>
</ol>
<hr />
<ol class="partners">
<li class="partner">
<a href="${reverse('university_profile', args=['WellesleyX'])}">
<img src="${static.url('images/university/wellesley/wellesley-rollover_350x150.png')}" />
<div class="name">
......@@ -94,7 +110,7 @@
</div>
</a>
</li>
<li class="partner last">
<li class="partner">
<a href="${reverse('university_profile', args=['GeorgetownX'])}">
<img src="${static.url('images/university/georgetown/georgetown-rollover_350x150.png')}" />
<div class="name">
......@@ -102,6 +118,38 @@
</div>
</a>
</li>
<li class="partner">
<a href="${reverse('university_profile', args=['TorontoX'])}">
<img src="${static.url('images/university/toronto/toronto.png')}" />
<div class="name">
<span>TorontoX</span>
</div>
</a>
</li>
<li class="partner">
<a href="${reverse('university_profile', args=['EPFLx'])}">
<img src="${static.url('images/university/epfl/epfl.png')}" />
<div class="name">
<span>EPFLx</span>
</div>
</a>
</li>
<li class="partner">
<a href="${reverse('university_profile', args=['DelftX'])}">
<img src="${static.url('images/university/delft/delft.png')}" />
<div class="name">
<span>DelftX</span>
</div>
</a>
</li>
<li class="partner">
<a href="${reverse('university_profile', args=['RiceX'])}">
<img src="${static.url('images/university/rice/rice.png')}" />
<div class="name">
<span>RiceX</span>
</div>
</a>
</li>
</ol>
</section>
......
......@@ -78,6 +78,7 @@
<p>
Flag as inappropriate content for later review <input class="flag-checkbox" type="checkbox" />
</p>
</div>
<div class="submission">
......
......@@ -44,6 +44,7 @@
<textarea name="feedback" placeholder="Feedback for student"
class="feedback-area" cols="70" ></textarea>
<p class="flag-student-container">Flag this submission for review by course staff (use if the submission contains inappropriate content): <input type="checkbox" class="flag-checkbox" value="student_is_flagged"></p>
<p class="answer-unknown-container">I do not know how to grade this question: <input type="checkbox" class="answer-unknown-checkbox" value="answer_is_unknown"></p>
</div>
......
<%! from django.core.urlresolvers import reverse %>
<%inherit file="../../main.html" />
<%namespace name='static' file='../../static_content.html'/>
<%block name="title"><title>edX Expands Internationally and Doubles its Institutional Membership with the Addition of Six New Schools</title></%block>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<section class="pressrelease">
<section class="container">
<h1>edX Expands Internationally and Doubles its Institutional Membership with the Addition of Six New Schools</h1>
<hr class="horizontal-divider">
<article>
<h2>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</h2>
<p><strong>CAMBRIDGE, MA &ndash; Feb. 20, 2013 &ndash;</strong>
<a href="https://www.edx.org/">EdX</a>, 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.
</p>
<p>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:
</p>
<ul>
<li>The Australian National University, a celebrated place of intensive research, education and policy engagement, will provide a series of <a href="https://www.edx.org/university_profile/ANUx">ANUx</a> 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.</li>
<li>Delft University of Technology, the largest and oldest technological university in the Netherlands, will provide a series of <a href="https://www.edx.org/university_profile/DelftX">DelftX</a> 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.</li>
<li>École Polytechnique Fédérale de Lausanne, one of the most famous institutions of science and technology in Europe, will provide a series of <a href="https://www.edx.org/university_profile/EPFLx">EPFLx</a> courses specially tailored to fit the edX format, originating from its five schools -- Engineering, Life Sciences, Informatics and Communication, Architecture and Basic Sciences.</li>
<li>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 <a href="https://www.edx.org/university_profile/McGillX">McGillX</a> courses in areas ranging from science and the humanities to public policy issues.</li>
<li>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 <a href="https://www.edx.org/university_profile/RiceX">RiceX</a> 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.</li>
<li>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 <a href="https://www.edx.org/university_profile/TorontoX">TorontoX</a> 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.</li>
</ul>
<p>“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.”</p>
<p>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.</p>
<p>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.
</p>
<h2>About edX</h2>
<p><a href="https://www.edx.org/">EdX</a> is a not-for-profit enterprise of its founding partners <a href="http://www.harvard.edu">Harvard University</a> and the <a href="http://www.mit.edu">Massachusetts Institute of Technology</a> 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.</p>
<section class="contact">
<p><strong>Media Contact:</strong></p>
<p>Dan O'Connell</p>
<p>oconnell@edx.org</p>
<p>(617) 480-6585</p>
</section>
<section class="footer">
<hr class="horizontal-divider">
<div class="logo"></div><h3 class="date">02 - 20 - 2013</h3>
<div class="social-sharing">
<hr class="horizontal-divider">
<p>Share with friends and family:</p>
<a href="http://twitter.com/intent/tweet?text=:edX+expands+internationally+http://www.edx.org/press/edx-expands-internationally" class="share">
<img src="${static.url('images/social/twitter-sharing.png')}">
</a>
</a>
<a href="mailto:?subject=edX%20expands%20internationally…http://edx.org/press/edx-expands-internationally" class="share">
<img src="${static.url('images/social/email-sharing.png')}">
</a>
<div class="fb-like" data-href="http://edx.org/press/edx-expands-internationally" data-send="true" data-width="450" data-show-faces="true"></div>
</div>
</section>
</article>
</section>
</section>
......@@ -33,10 +33,10 @@ Text</p>
<section class="contact">
<p><strong>Contact:</strong></p>
<p>Brad Baker, Weber Shandwick for edX</p>
<p>BBaker@webershandwick.com</p>
<p>(617) 520-7043</p>
<p><strong>Media Contact:</strong></p>
<p>Dan O'Connell</p>
<p>oconnell@edx.org</p>
<p>(617) 480-6585</p>
</section>
<section class="footer">
......@@ -49,7 +49,7 @@ Text</p>
<img src="${static.url('images/social/twitter-sharing.png')}">
</a>
</a>
<a href="mailto:?subject=BLAH%BLAH%BLAH…http://edx.org/press/LINK" class="share">
<a href="mailto:?subject=BLAH%20BLAH%20BLAH…http://edx.org/press/LINK" class="share">
<img src="${static.url('images/social/email-sharing.png')}">
</a>
<div class="fb-like" data-href="http://edx.org/press/LINK" data-send="true" data-width="450" data-show-faces="true"></div>
......
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="title"><title>ANUx</title></%block>
<%block name="university_header">
<header class="search" style="background: url('/static/images/university/anu/anu-cover.jpg')">
<div class="inner-wrapper university-search">
<hgroup>
<div class="logo">
<img src="${static.url('images/university/anu/anu.png')}" />
</div>
<h1>ANUx</h1>
</hgroup>
</div>
</header>
</%block>
<%block name="university_description">
<p>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.</p>
</%block>
${parent.body()}
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="title"><title>DelftX</title></%block>
<%block name="university_header">
<header class="search" style="background: url('/static/images/university/delft/delft-cover.jpg')">
<div class="inner-wrapper university-search">
<hgroup>
<div class="logo">
<img src="${static.url('images/university/delft/delft.png')}" />
</div>
<h1>DelftX</h1>
</hgroup>
</div>
</header>
</%block>
<%block name="university_description">
<p>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.</p>
</%block>
${parent.body()}
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="title"><title>EPFLx</title></%block>
<%block name="university_header">
<header class="search" style="background: url('/static/images/university/epfl/epfl-cover.jpg')">
<div class="inner-wrapper university-search">
<hgroup>
<div class="logo">
<img src="${static.url('images/university/epfl/epfl.png')}" />
</div>
<h1>EPFLx</h1>
</hgroup>
</div>
</header>
</%block>
<%block name="university_description">
<p>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. </p>
<p>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.
</p>
</%block>
${parent.body()}
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="title"><title>McGillX</title></%block>
<%block name="university_header">
<header class="search" style="background: url('/static/images/university/mcgill/mcgill-cover.jpg')">
<div class="inner-wrapper university-search">
<hgroup>
<div class="logo">
<img src="${static.url('images/university/mcgill/mcgill.png')}" />
</div>
<h1>McGillX</h1>
</hgroup>
</div>
</header>
</%block>
<%block name="university_description">
<p>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).</p>
</%block>
${parent.body()}
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="title"><title>RiceX</title></%block>
<%block name="university_header">
<header class="search" style="background: url('/static/images/university/rice/rice-cover.jpg')">
<div class="inner-wrapper university-search">
<hgroup>
<div class="logo">
<img src="${static.url('images/university/rice/rice.png')}" />
</div>
<h1>RiceX</h1>
</hgroup>
</div>
</header>
</%block>
<%block name="university_description">
<p>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.</p>
</%block>
${parent.body()}
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="title"><title>SCHOOLX</title></%block>
<%block name="university_header">
<header class="search" style="background: url('/static/images/university/SCHOOL/IMAGE.jpg')">
<div class="inner-wrapper university-search">
<hgroup>
<div class="logo">
<img src="${static.url('images/university/SCHOOL/IMAGE.png')}" />
</div>
<h1>SCHOOLX</h1>
</hgroup>
</div>
</header>
</%block>
<%block name="university_description">
<p></p>
</%block>
${parent.body()}
<%inherit file="base.html" />
<%namespace name='static' file='../static_content.html'/>
<%block name="title"><title>TorontoX</title></%block>
<%block name="university_header">
<header class="search" style="background: url('/static/images/university/toronto/toronto-cover.jpg')">
<div class="inner-wrapper university-search">
<hgroup>
<div class="logo">
<img src="${static.url('images/university/toronto/toronto.png')}" />
</div>
<h1>TorontoX</h1>
</hgroup>
</div>
</header>
</%block>
<%block name="university_description">
<p>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.</p>
</%block>
${parent.body()}
......@@ -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<org_id>[^/]+)$', '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<org_id>[^/]+)$', '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'}),
......
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