Commit 47ce9082 by Diana Huang

Merge branch 'release'

Conflicts:
	common/lib/xmodule/xmodule/modulestore/mongo/draft.py
	lms/djangoapps/courseware/tests/modulestore_config.py
parents 4d99ef7e 97a69e56
...@@ -353,7 +353,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -353,7 +353,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
def test_create_static_tab_and_rename(self): def test_create_static_tab_and_rename(self):
module_store = modulestore('direct') module_store = modulestore('direct')
CourseFactory.create(org='edX', course='999', display_name='Robot Super Course') CourseFactory.create(org='edX', course='999', display_name='Robot Super Course')
course_location = Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None]) course_location = Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None])
item = ItemFactory.create(parent_location=course_location, category='static_tab', display_name="My Tab") item = ItemFactory.create(parent_location=course_location, category='static_tab', display_name="My Tab")
...@@ -735,7 +735,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -735,7 +735,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# we want to assert equality between the objects, but we know the locations # we want to assert equality between the objects, but we know the locations
# differ, so just make them equal for testing purposes # differ, so just make them equal for testing purposes
source_item.scope_ids = source_item.scope_ids._replace(def_id=new_loc, usage_id=new_loc) source_item.location = new_loc
if hasattr(source_item, 'data') and hasattr(lookup_item, 'data'): if hasattr(source_item, 'data') and hasattr(lookup_item, 'data'):
self.assertEqual(source_item.data, lookup_item.data) self.assertEqual(source_item.data, lookup_item.data)
...@@ -916,8 +916,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -916,8 +916,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
depth=1 depth=1
) )
# We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case. # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case.
draft_loc = mongo.draft.as_draft(vertical.location.replace(name='no_references')) vertical.location = mongo.draft.as_draft(vertical.location.replace(name='no_references'))
vertical.scope_ids = vertical.scope_ids._replace(def_id=draft_loc, usage_id=draft_loc)
draft_store.save_xmodule(vertical) draft_store.save_xmodule(vertical)
orphan_vertical = draft_store.get_item(vertical.location) orphan_vertical = draft_store.get_item(vertical.location)
...@@ -935,8 +934,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -935,8 +934,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
root_dir = path(mkdtemp_clean()) root_dir = path(mkdtemp_clean())
# now create a new/different private (draft only) vertical # now create a new/different private (draft only) vertical
draft_loc = mongo.draft.as_draft(Location(['i4x', 'edX', 'toy', 'vertical', 'a_private_vertical', None])) vertical.location = mongo.draft.as_draft(Location(['i4x', 'edX', 'toy', 'vertical', 'a_private_vertical', None]))
vertical.scope_ids = vertical.scope_ids._replace(def_id=draft_loc, usage_id=draft_loc)
draft_store.save_xmodule(vertical) draft_store.save_xmodule(vertical)
private_vertical = draft_store.get_item(vertical.location) private_vertical = draft_store.get_item(vertical.location)
vertical = None # blank out b/c i destructively manipulated its location 2 lines above vertical = None # blank out b/c i destructively manipulated its location 2 lines above
...@@ -985,7 +983,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -985,7 +983,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertEqual(on_disk['course/2012_Fall'], own_metadata(course)) self.assertEqual(on_disk['course/2012_Fall'], own_metadata(course))
# remove old course # remove old course
delete_course(module_store, content_store, location) delete_course(module_store, content_store, location, commit=True)
# reimport # reimport
import_from_xml(module_store, root_dir, ['test_export'], draft_store=draft_store) import_from_xml(module_store, root_dir, ['test_export'], draft_store=draft_store)
......
...@@ -41,8 +41,8 @@ def wrap_draft(item): ...@@ -41,8 +41,8 @@ def wrap_draft(item):
draft, and `False` otherwise. Sets the item's location to the draft, and `False` otherwise. Sets the item's location to the
non-draft location in either case non-draft location in either case
""" """
item.is_draft = (item.location.revision == DRAFT) setattr(item, 'is_draft', item.location.revision == DRAFT)
item.scope_ids = item.scope_ids._replace(usage_id=item.location.replace(revision=None)) item.location = item.location.replace(revision=None)
return item return item
......
...@@ -110,27 +110,19 @@ def _clone_modules(modulestore, modules, source_location, dest_location): ...@@ -110,27 +110,19 @@ def _clone_modules(modulestore, modules, source_location, dest_location):
original_loc = Location(module.location) original_loc = Location(module.location)
if original_loc.category != 'course': if original_loc.category != 'course':
new_location = module.location._replace( module.location = module.location._replace(
tag=dest_location.tag, tag=dest_location.tag,
org=dest_location.org, org=dest_location.org,
course=dest_location.course course=dest_location.course
) )
module.scope_ids = module.scope_ids._replace(
def_id=new_location,
usage_id=new_location
)
else: else:
# on the course module we also have to update the module name # on the course module we also have to update the module name
new_location = module.location._replace( module.location = module.location._replace(
tag=dest_location.tag, tag=dest_location.tag,
org=dest_location.org, org=dest_location.org,
course=dest_location.course, course=dest_location.course,
name=dest_location.name name=dest_location.name
) )
module.scope_ids = module.scope_ids._replace(
def_id=new_location,
usage_id=new_location
)
print "Cloning module {0} to {1}....".format(original_loc, module.location) print "Cloning module {0} to {1}....".format(original_loc, module.location)
......
...@@ -375,30 +375,22 @@ def remap_namespace(module, target_location_namespace): ...@@ -375,30 +375,22 @@ def remap_namespace(module, target_location_namespace):
# This looks a bit wonky as we need to also change the 'name' of the imported course to be what # This looks a bit wonky as we need to also change the 'name' of the imported course to be what
# the caller passed in # the caller passed in
if module.location.category != 'course': if module.location.category != 'course':
new_location = module.location._replace( module.location = module.location._replace(
tag=target_location_namespace.tag, tag=target_location_namespace.tag,
org=target_location_namespace.org, org=target_location_namespace.org,
course=target_location_namespace.course course=target_location_namespace.course
) )
module.scope_ids = module.scope_ids._replace(
def_id=new_location,
usage_id=new_location
)
else: else:
original_location = module.location original_location = module.location
# #
# module is a course module # module is a course module
# #
new_location = module.location._replace( module.location = module.location._replace(
tag=target_location_namespace.tag, tag=target_location_namespace.tag,
org=target_location_namespace.org, org=target_location_namespace.org,
course=target_location_namespace.course, course=target_location_namespace.course,
name=target_location_namespace.name name=target_location_namespace.name
) )
module.scope_ids = module.scope_ids._replace(
def_id=new_location,
usage_id=new_location
)
# #
# There is more re-namespacing work we have to do when importing course modules # There is more re-namespacing work we have to do when importing course modules
# #
......
...@@ -94,9 +94,9 @@ class MockPeerGradingService(object): ...@@ -94,9 +94,9 @@ class MockPeerGradingService(object):
'success': True, 'success': True,
'submission_id': 1, 'submission_id': 1,
'submission_key': "", 'submission_key': "",
'student_response': 'fake student response', 'student_response': 'Sample student response.',
'prompt': 'fake submission prompt', 'prompt': 'Sample submission prompt.',
'rubric': 'fake rubric', 'rubric': 'Placeholder text for the full rubric.',
'max_score': 4 'max_score': 4
} }
...@@ -110,9 +110,9 @@ class MockPeerGradingService(object): ...@@ -110,9 +110,9 @@ class MockPeerGradingService(object):
return {'success': True, return {'success': True,
'submission_id': 1, 'submission_id': 1,
'submission_key': '', 'submission_key': '',
'student_response': 'fake student response', 'student_response': 'Sample student response.',
'prompt': 'fake submission prompt', 'prompt': 'Sample submission prompt.',
'rubric': 'fake rubric', 'rubric': 'Placeholder text for the full rubric.',
'max_score': 4} 'max_score': 4}
def save_calibration_essay(self, **kwargs): def save_calibration_essay(self, **kwargs):
......
...@@ -8,7 +8,7 @@ from pkg_resources import resource_string ...@@ -8,7 +8,7 @@ from pkg_resources import resource_string
from .capa_module import ComplexEncoder from .capa_module import ComplexEncoder
from .x_module import XModule from .x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from .timeinfo import TimeInfo from .timeinfo import TimeInfo
from xblock.fields import Dict, String, Scope, Boolean, Float from xblock.fields import Dict, String, Scope, Boolean, Float
from xmodule.fields import Date, Timedelta from xmodule.fields import Date, Timedelta
...@@ -72,6 +72,12 @@ class PeerGradingFields(object): ...@@ -72,6 +72,12 @@ class PeerGradingFields(object):
scope=Scope.content scope=Scope.content
) )
class InvalidLinkLocation(Exception):
"""
Exception for the case in which a peer grading module tries to link to an invalid location.
"""
pass
class PeerGradingModule(PeerGradingFields, XModule): class PeerGradingModule(PeerGradingFields, XModule):
""" """
...@@ -103,11 +109,21 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -103,11 +109,21 @@ class PeerGradingModule(PeerGradingFields, XModule):
if self.use_for_single_location: if self.use_for_single_location:
try: try:
self.linked_problem = self.system.get_module(self.link_to_location) linked_descriptors = self.descriptor.get_required_module_descriptors()
if len(linked_descriptors) == 0:
error_msg = "Peer grading module {0} is trying to use single problem mode without "
"a location specified.".format(self.location)
log.error(error_msg)
raise InvalidLinkLocation(error_msg)
self.linked_problem = self.system.get_module(linked_descriptors[0])
except ItemNotFoundError: except ItemNotFoundError:
log.error("Linked location {0} for peer grading module {1} does not exist".format( log.error("Linked location {0} for peer grading module {1} does not exist".format(
self.link_to_location, self.location)) self.link_to_location, self.location))
raise raise
except NoPathToItem:
log.error("Linked location {0} for peer grading module {1} cannot be linked to.".format(
self.link_to_location, self.location))
raise
due_date = self.linked_problem.due due_date = self.linked_problem.due
if due_date: if due_date:
self.due = due_date self.due = due_date
...@@ -514,14 +530,18 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -514,14 +530,18 @@ class PeerGradingModule(PeerGradingFields, XModule):
def _find_corresponding_module_for_location(location): def _find_corresponding_module_for_location(location):
''' """
find the peer grading module that links to the given location Find the peer grading module that exists at the given location.
''' """
try: try:
return modulestore().get_instance(self.system.course_id, location) return self.descriptor.system.load_item(location)
except Exception: except ItemNotFoundError:
# the linked problem doesn't exist # The linked problem doesn't exist.
log.error("Problem {0} does not exist in this course".format(location)) log.error("Problem {0} does not exist in this course.".format(location))
raise
except NoPathToItem:
# The linked problem does not have a path to it (ie is in a draft or other strange state).
log.error("Cannot find a path to problem {0} in this course.".format(location))
raise raise
good_problem_list = [] good_problem_list = []
...@@ -529,7 +549,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -529,7 +549,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
problem_location = problem['location'] problem_location = problem['location']
try: try:
descriptor = _find_corresponding_module_for_location(problem_location) descriptor = _find_corresponding_module_for_location(problem_location)
except Exception: except (NoPathToItem, ItemNotFoundError):
continue continue
if descriptor: if descriptor:
problem['due'] = descriptor.due problem['due'] = descriptor.due
......
...@@ -2,6 +2,12 @@ import unittest ...@@ -2,6 +2,12 @@ import unittest
from xmodule.modulestore import Location from xmodule.modulestore import Location
from .import get_test_system from .import get_test_system
from test_util_open_ended import MockQueryDict, DummyModulestore from test_util_open_ended import MockQueryDict, DummyModulestore
from xmodule.open_ended_grading_classes.peer_grading_service import MockPeerGradingService
import json
from mock import Mock
from xmodule.peer_grading_module import PeerGradingModule
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
import logging import logging
...@@ -136,6 +142,13 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore): ...@@ -136,6 +142,13 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
""" """
self.peer_grading.get_instance_state() self.peer_grading.get_instance_state()
class MockPeerGradingServiceProblemList(MockPeerGradingService):
def get_problem_list(self, course_id, grader_id):
return {'success': True,
'problem_list': [
{"num_graded": 3, "num_pending": 681, "num_required": 3, "location": "i4x://edX/open_ended/combinedopenended/SampleQuestion", "problem_name": "Peer-Graded Essay"},
]}
class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore): class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
""" """
Test peer grading xmodule at the unit level. More detailed tests are difficult, as the module relies on an Test peer grading xmodule at the unit level. More detailed tests are difficult, as the module relies on an
...@@ -155,3 +168,70 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore): ...@@ -155,3 +168,70 @@ class PeerGradingModuleScoredTest(unittest.TestCase, DummyModulestore):
def test_metadata_load(self): def test_metadata_load(self):
peer_grading = self.get_module_from_location(self.problem_location, COURSE) peer_grading = self.get_module_from_location(self.problem_location, COURSE)
self.assertEqual(peer_grading.closed(), False) self.assertEqual(peer_grading.closed(), False)
def test_problem_list(self):
"""
Test to see if a peer grading problem list can be correctly initialized.
"""
# Initialize peer grading module.
peer_grading = self.get_module_from_location(self.problem_location, COURSE)
# Ensure that it cannot find any peer grading.
html = peer_grading.peer_grading()
self.assertNotIn("Peer-Graded", html)
# Swap for our mock class, which will find peer grading.
peer_grading.peer_gs = MockPeerGradingServiceProblemList()
html = peer_grading.peer_grading()
self.assertIn("Peer-Graded", html)
class PeerGradingModuleLinkedTest(unittest.TestCase, DummyModulestore):
"""
Test peer grading that is linked to an open ended module.
"""
problem_location = Location(["i4x", "edX", "open_ended", "peergrading",
"PeerGradingLinked"])
coe_location = Location(["i4x", "edX", "open_ended", "combinedopenended",
"SampleQuestion"])
def setUp(self):
"""
Create a peer grading module from a test system.
"""
self.test_system = get_test_system()
self.test_system.open_ended_grading_interface = None
self.setup_modulestore(COURSE)
def test_linked_problem(self):
"""
Check to see if a peer grading module with a linked problem loads properly.
"""
# Mock the linked problem descriptor.
linked_descriptor = Mock()
linked_descriptor.location = self.coe_location
# Mock the peer grading descriptor.
pg_descriptor = Mock()
pg_descriptor.location = self.problem_location
pg_descriptor.get_required_module_descriptors = lambda: [linked_descriptor, ]
# Setup the proper field data for the peer grading module.
field_data = DictFieldData({
'data': '<peergrading/>',
'location': self.problem_location,
'use_for_single_location': True,
'link_to_location': self.coe_location,
})
# Initialize the peer grading module.
peer_grading = PeerGradingModule(
pg_descriptor,
self.test_system,
field_data,
ScopeIds(None, None, self.problem_location, self.problem_location)
)
# Ensure that it is properly setup.
self.assertTrue(peer_grading.use_for_single_location)
...@@ -146,6 +146,13 @@ class XModule(XModuleFields, HTMLSnippet, XBlock): ...@@ -146,6 +146,13 @@ class XModule(XModuleFields, HTMLSnippet, XBlock):
else: else:
return BlockUsageLocator(self.scope_ids.usage_id) return BlockUsageLocator(self.scope_ids.usage_id)
@location.setter
def location(self, value):
self.scope_ids = self.scope_ids._replace(
def_id=value,
usage_id=value,
)
@property @property
def url_name(self): def url_name(self):
if self.descriptor: if self.descriptor:
...@@ -457,6 +464,13 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): ...@@ -457,6 +464,13 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
else: else:
return BlockUsageLocator(self.scope_ids.usage_id) return BlockUsageLocator(self.scope_ids.usage_id)
@location.setter
def location(self, value):
self.scope_ids = self.scope_ids._replace(
def_id=value,
usage_id=value,
)
@property @property
def url_name(self): def url_name(self):
if isinstance(self.location, Location): if isinstance(self.location, Location):
......
...@@ -93,7 +93,7 @@ $(document).ready(function() { ...@@ -93,7 +93,7 @@ $(document).ready(function() {
% if "honor" in modes: % if "honor" in modes:
<dt class="faq-question">${_("What if I can't afford it or don't have the necessary equipment?")}</dt> <dt class="faq-question">${_("What if I can't afford it or don't have the necessary equipment?")}</dt>
<dd class="faq-answer"> <dd class="faq-answer">
<p>${_("If you can't afford the minimum fee, don't have a webcam, credit card, debit card or acceptable ID, you can audit the course for free. You may also elect to pursue an Honor Code certificate, but you will need to tell us why you would like the fee waived below. Then click the 'Select Certificate' button to complete your registration.")}</p> <p>${_("If you can't afford the minimum fee or don't meet the requirements, you can audit the course for free. You may also elect to pursue an Honor Code certificate, but you will need to tell us why you would like the fee waived below. Then click the 'Select Certificate' button to complete your registration.")}</p>
<ul class="list-fields"> <ul class="list-fields">
<li class="field field-honor-code checkbox"> <li class="field field-honor-code checkbox">
......
...@@ -4,5 +4,6 @@ ...@@ -4,5 +4,6 @@
<combinedopenended url_name="SampleQuestion1Attempt"/> <combinedopenended url_name="SampleQuestion1Attempt"/>
<peergrading url_name="PeerGradingSample"/> <peergrading url_name="PeerGradingSample"/>
<peergrading url_name="PeerGradingScored"/> <peergrading url_name="PeerGradingScored"/>
<peergrading url_name="PeerGradingLinked"/>
</chapter> </chapter>
</course> </course>
<peergrading is_graded="True" max_grade="1" use_for_single_location="True" link_to_location="i4x://edX/open_ended/combinedopenended/SampleQuestion"/>
\ No newline at end of file
This is a very very simple course, useful for debugging open ended grading code. This is specifically for testing if a peer grading module with no path to it in the course will be handled properly.
<course org="edX" course="open_ended_nopath" url_name="2012_Fall"/>
<course>
<chapter url_name="Overview">
</chapter>
</course>
{
"course/2012_Fall": {
"graceperiod": "2 days 5 hours 59 minutes 59 seconds",
"start": "2015-07-17T12:00",
"display_name": "Self Assessment Test",
"graded": "true"
},
"chapter/Overview": {
"display_name": "Overview"
}
}
...@@ -22,6 +22,7 @@ MAPPINGS = { ...@@ -22,6 +22,7 @@ MAPPINGS = {
'edX/test_about_blob_end_date/2012_Fall': 'xml', 'edX/test_about_blob_end_date/2012_Fall': 'xml',
'edX/graded/2012_Fall': 'xml', 'edX/graded/2012_Fall': 'xml',
'edX/open_ended/2012_Fall': 'xml', 'edX/open_ended/2012_Fall': 'xml',
'edX/due_date/2013_fall': 'xml' 'edX/due_date/2013_fall': 'xml',
'edX/open_ended_nopath/2012_Fall': 'xml',
} }
TEST_DATA_MIXED_MODULESTORE = mixed_store_config(TEST_DATA_DIR, MAPPINGS) TEST_DATA_MIXED_MODULESTORE = mixed_store_config(TEST_DATA_DIR, MAPPINGS)
...@@ -320,3 +320,22 @@ class TestPanel(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -320,3 +320,22 @@ class TestPanel(ModuleStoreTestCase, LoginEnrollmentTestCase):
request = Mock(user=self.user) request = Mock(user=self.user)
response = views.student_problem_list(request, self.course.id) response = views.student_problem_list(request, self.course.id)
self.assertRegexpMatches(response.content, "Here are a list of open ended problems for this course.") self.assertRegexpMatches(response.content, "Here are a list of open ended problems for this course.")
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestPeerGradingFound(ModuleStoreTestCase):
"""
Test to see if peer grading modules can be found properly.
"""
def setUp(self):
self.course_name = 'edX/open_ended_nopath/2012_Fall'
self.course = modulestore().get_course(self.course_name)
def test_peer_grading_nopath(self):
"""
The open_ended_nopath course contains a peer grading module with no path to it.
Ensure that the exception is caught.
"""
found, url = views.find_peer_grading_module(self.course)
self.assertEqual(found, False)
\ No newline at end of file
...@@ -20,7 +20,7 @@ import open_ended_notifications ...@@ -20,7 +20,7 @@ import open_ended_notifications
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import search from xmodule.modulestore import search
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from django.http import HttpResponse, Http404, HttpResponseRedirect from django.http import HttpResponse, Http404, HttpResponseRedirect
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
...@@ -97,25 +97,32 @@ def find_peer_grading_module(course): ...@@ -97,25 +97,32 @@ def find_peer_grading_module(course):
@param course: A course object. @param course: A course object.
@return: boolean found_module, string problem_url @return: boolean found_module, string problem_url
""" """
#Reverse the base course url
# Reverse the base course url.
base_course_url = reverse('courses') base_course_url = reverse('courses')
found_module = False found_module = False
problem_url = "" problem_url = ""
#Get the course id and split it # Get the course id and split it.
course_id_parts = course.id.split("/") course_id_parts = course.id.split("/")
log.info("COURSE ID PARTS") log.info("COURSE ID PARTS")
log.info(course_id_parts) log.info(course_id_parts)
#Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs. # Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs.
items = modulestore().get_items(['i4x', course_id_parts[0], course_id_parts[1], 'peergrading', None], items = modulestore().get_items(['i4x', course_id_parts[0], course_id_parts[1], 'peergrading', None],
course_id=course.id) course_id=course.id)
#See if any of the modules are centralized modules (ie display info from multiple problems) #See if any of the modules are centralized modules (ie display info from multiple problems)
items = [i for i in items if not getattr(i, "use_for_single_location", True)] items = [i for i in items if not getattr(i, "use_for_single_location", True)]
#Get the first one # Loop through all potential peer grading modules, and find the first one that has a path to it.
if len(items) > 0: for item in items:
item_location = items[0].location item_location = item.location
#Generate a url for the first module and redirect the user to it # Generate a url for the first module and redirect the user to it.
problem_url_parts = search.path_to_location(modulestore(), course.id, item_location) try:
problem_url_parts = search.path_to_location(modulestore(), course.id, item_location)
except NoPathToItem:
# In the case of nopathtoitem, the peer grading module that was found is in an invalid state, and
# can no longer be accessed. Log an informational message, but this will not impact normal behavior.
log.info("Invalid peer grading module location {0} in course {1}. This module may need to be removed.".format(item_location, course.id))
continue
problem_url = generate_problem_url(problem_url_parts, base_course_url) problem_url = generate_problem_url(problem_url_parts, base_course_url)
found_module = True found_module = True
......
...@@ -476,7 +476,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification): ...@@ -476,7 +476,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
body = { body = {
"EdX-ID": str(self.receipt_id), "EdX-ID": str(self.receipt_id),
"ExpectedName": self.user.profile.name, "ExpectedName": self.name,
"PhotoID": self.image_url("photo_id"), "PhotoID": self.image_url("photo_id"),
"PhotoIDKey": self.photo_id_key, "PhotoIDKey": self.photo_id_key,
"SendResponseTo": callback_url, "SendResponseTo": callback_url,
...@@ -500,7 +500,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification): ...@@ -500,7 +500,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
header_txt = "\n".join( header_txt = "\n".join(
"{}: {}".format(h, v) for h,v in sorted(headers.items()) "{}: {}".format(h, v) for h,v in sorted(headers.items())
) )
body_txt = json.dumps(body, indent=2, sort_keys=True) body_txt = json.dumps(body, indent=2, sort_keys=True, ensure_ascii=False)
return header_txt + "\n\n" + body_txt return header_txt + "\n\n" + body_txt
...@@ -509,7 +509,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification): ...@@ -509,7 +509,7 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
response = requests.post( response = requests.post(
settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_URL"], settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_URL"],
headers=headers, headers=headers,
data=json.dumps(body, indent=2, sort_keys=True) data=json.dumps(body, indent=2, sort_keys=True, ensure_ascii=False)
) )
log.debug("Sent request to Software Secure for {}".format(self.receipt_id)) log.debug("Sent request to Software Secure for {}".format(self.receipt_id))
log.debug("Headers:\n{}\n\n".format(headers)) log.debug("Headers:\n{}\n\n".format(headers))
......
...@@ -131,7 +131,8 @@ def generate_signed_message(method, headers_dict, body_dict, access_key, secret_ ...@@ -131,7 +131,8 @@ def generate_signed_message(method, headers_dict, body_dict, access_key, secret_
body_str = body_string(body_dict) body_str = body_string(body_dict)
message = headers_str + body_str message = headers_str + body_str
hashed = hmac.new(secret_key, message, sha256) # hmac needs a byte string for it's starting key, can't be unicode.
hashed = hmac.new(secret_key.encode('utf-8'), message, sha256)
signature = binascii.b2a_base64(hashed.digest()).rstrip('\n') signature = binascii.b2a_base64(hashed.digest()).rstrip('\n')
authorization_header = "SSI {}:{}".format(access_key, signature) authorization_header = "SSI {}:{}".format(access_key, signature)
...@@ -161,7 +162,7 @@ def body_string(body_dict): ...@@ -161,7 +162,7 @@ def body_string(body_dict):
for key, value in sorted(body_dict.items()): for key, value in sorted(body_dict.items()):
if value is None: if value is None:
value = "null" value = "null"
body_list.append(u"{}:{}\n".format(key, value)) body_list.append(u"{}:{}\n".format(key, value).encode('utf-8'))
return "".join(body_list) # Note that trailing \n's are important return "".join(body_list) # Note that trailing \n's are important
...@@ -50,7 +50,11 @@ var submitToPaymentProcessing = function() { ...@@ -50,7 +50,11 @@ var submitToPaymentProcessing = function() {
$("#pay_form").submit(); $("#pay_form").submit();
}) })
.fail(function(jqXhr,text_status, error_thrown) { .fail(function(jqXhr,text_status, error_thrown) {
alert(jqXhr.responseText); if(jqXhr.status == 400) {
$('#order-error .copy p').html(jqXhr.responseText);
}
$('#order-error').show();
$("html, body").animate({ scrollTop: 0 });
}); });
} }
......
...@@ -44,7 +44,7 @@ site_status_msg = get_site_status_msg(course_id) ...@@ -44,7 +44,7 @@ site_status_msg = get_site_status_msg(course_id)
<h1 class="logo"> <h1 class="logo">
<a href="${marketing_link('ROOT')}"> <a href="${marketing_link('ROOT')}">
<%block name="navigation_logo"> <%block name="navigation_logo">
<img src="${static.url(branding.get_logo_url(request.META.get('HTTP_HOST')))}" alt="${_('{settings.PLATFORM_NAME} home')}" /> <img src="${static.url(branding.get_logo_url(request.META.get('HTTP_HOST')))}" alt="${settings.PLATFORM_NAME} ${_('Home')}" />
</%block> </%block>
</a> </a>
</h1> </h1>
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<span class="sts-track"> <span class="sts-track">
<span class="sts-track-value"> <span class="sts-track-value">
<span class="context">${_("Registering as: ")}</span> ${_("ID Verified")} <span class="context">${_("Registered as: ")}</span> ${_("ID Verified")}
</span> </span>
</span> </span>
</span> </span>
......
...@@ -38,6 +38,17 @@ ...@@ -38,6 +38,17 @@
</div> </div>
</div> </div>
<div id="order-error" style="display: none;" class="wrapper-msg wrapper-msg-activate">
<div class=" msg msg-activate">
<i class="msg-icon icon-warning-sign"></i>
<div class="msg-content">
<h3 class="title">${_("Error processing your order")}</h3>
<div class="copy">
<p>${_("Oops! Something went wrong. Please confirm your details again and click the button to move on to payment. If you are still having trouble, please try again later.")}</p>
</div>
</div>
</div>
</div>
<div class="container"> <div class="container">
<section class="wrapper"> <section class="wrapper">
...@@ -309,7 +320,7 @@ ...@@ -309,7 +320,7 @@
<ul class="list-help list-tips copy"> <ul class="list-help list-tips copy">
<li class="tip">${_("Be readable (not too far away, no glare)")}</li> <li class="tip">${_("Be readable (not too far away, no glare)")}</li>
<li class="tip">${_("The photo on your ID must match the photo of your face")}</li> <li class="tip">${_("The photo on your ID must match the photo of your face")}</li>
<li class="tip">${_("The name on your ID must match the name on your account above")}</li> <li class="tip">${_("The name on your ID must match the name on your account below")}</li>
</ul> </ul>
</div> </div>
</li> </li>
......
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