Fix tests post-merge

This commit is contained in:
Calen Pennington
2013-02-13 14:13:22 -05:00
parent 793bbfd351
commit bd822b9d2f
22 changed files with 107 additions and 121 deletions

View File

@@ -44,9 +44,8 @@ from collections import namedtuple
from courseware.courses import get_courses, sort_by_announcement
from courseware.access import has_access
from courseware.models import StudentModuleCache
from courseware.views import get_module_for_descriptor, jump_to
from courseware.module_render import get_instance_module
from courseware.model_data import ModelDataCache
from statsd import statsd
@@ -1071,14 +1070,14 @@ def accept_name_change(request):
@csrf_exempt
def test_center_login(request):
# errors are returned by navigating to the error_url, adding a query parameter named "code"
# errors are returned by navigating to the error_url, adding a query parameter named "code"
# which contains the error code describing the exceptional condition.
def makeErrorURL(error_url, error_code):
log.error("generating error URL with error code {}".format(error_code))
return "{}?code={}".format(error_url, error_code);
# get provided error URL, which will be used as a known prefix for returning error messages to the
# Pearson shell.
# Pearson shell.
error_url = request.POST.get("errorURL")
# TODO: check that the parameters have not been tampered with, by comparing the code provided by Pearson
@@ -1089,12 +1088,12 @@ def test_center_login(request):
# calculate SHA for query string
# TODO: figure out how to get the original query string, so we can hash it and compare.
if 'clientCandidateID' not in request.POST:
return HttpResponseRedirect(makeErrorURL(error_url, "missingClientCandidateID"));
client_candidate_id = request.POST.get("clientCandidateID")
# TODO: check remaining parameters, and maybe at least log if they're not matching
# expected values....
# registration_id = request.POST.get("registrationID")
@@ -1108,12 +1107,12 @@ def test_center_login(request):
return HttpResponseRedirect(makeErrorURL(error_url, "invalidClientCandidateID"));
# find testcenter_registration that matches the provided exam code:
# Note that we could rely in future on either the registrationId or the exam code,
# or possibly both. But for now we know what to do with an ExamSeriesCode,
# Note that we could rely in future on either the registrationId or the exam code,
# or possibly both. But for now we know what to do with an ExamSeriesCode,
# while we currently have no record of RegistrationID values at all.
if 'vueExamSeriesCode' not in request.POST:
# we are not allowed to make up a new error code, according to Pearson,
# so instead of "missingExamSeriesCode", we use a valid one that is
# we are not allowed to make up a new error code, according to Pearson,
# so instead of "missingExamSeriesCode", we use a valid one that is
# inaccurate but at least distinct. (Sigh.)
log.error("missing exam series code for cand ID {}".format(client_candidate_id))
return HttpResponseRedirect(makeErrorURL(error_url, "missingPartnerID"));
@@ -1127,11 +1126,11 @@ def test_center_login(request):
if not registrations:
log.error("not able to find exam registration for exam {} and cand ID {}".format(exam_series_code, client_candidate_id))
return HttpResponseRedirect(makeErrorURL(error_url, "noTestsAssigned"));
# TODO: figure out what to do if there are more than one registrations....
# for now, just take the first...
registration = registrations[0]
course_id = registration.course_id
course = course_from_id(course_id) # assume it will be found....
if not course:
@@ -1149,19 +1148,19 @@ def test_center_login(request):
if not timelimit_descriptor:
log.error("cand {} on exam {} for course {}: descriptor not found for location {}".format(client_candidate_id, exam_series_code, course_id, location))
return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram"));
timelimit_module_cache = StudentModuleCache.cache_for_descriptor_descendents(course_id, testcenteruser.user,
timelimit_descriptor, depth=None)
timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor,
timelimit_module_cache = ModelDataCache.cache_for_descriptor_descendents(course_id, testcenteruser.user,
timelimit_descriptor, depth=None)
timelimit_module = get_module_for_descriptor(request.user, request, timelimit_descriptor,
timelimit_module_cache, course_id, position=None)
if not timelimit_module.category == 'timelimit':
log.error("cand {} on exam {} for course {}: non-timelimit module at location {}".format(client_candidate_id, exam_series_code, course_id, location))
return HttpResponseRedirect(makeErrorURL(error_url, "missingClientProgram"));
if timelimit_module and timelimit_module.has_ended:
log.warning("cand {} on exam {} for course {}: test already over at {}".format(client_candidate_id, exam_series_code, course_id, timelimit_module.ending_at))
return HttpResponseRedirect(makeErrorURL(error_url, "allTestsTaken"));
# check if we need to provide an accommodation:
time_accommodation_mapping = {'ET12ET' : 'ADDHALFTIME',
'ET30MN' : 'ADD30MIN',
@@ -1174,27 +1173,24 @@ def test_center_login(request):
# special, hard-coded client ID used by Pearson shell for testing:
if client_candidate_id == "edX003671291147":
time_accommodation_code = 'TESTING'
if time_accommodation_code:
timelimit_module.accommodation_code = time_accommodation_code
instance_module = get_instance_module(course_id, testcenteruser.user, timelimit_module, timelimit_module_cache)
instance_module.state = timelimit_module.get_instance_state()
instance_module.save()
log.info("cand {} on exam {} for course {}: receiving accommodation {}".format(client_candidate_id, exam_series_code, course_id, time_accommodation_code))
# UGLY HACK!!!
# Login assumes that authentication has occurred, and that there is a
# Login assumes that authentication has occurred, and that there is a
# backend annotation on the user object, indicating which backend
# against which the user was authenticated. We're authenticating here
# against the registration entry, and assuming that the request given
# this information is correct, we allow the user to be logged in
# without a password. This could all be formalized in a backend object
# that does the above checking.
# that does the above checking.
# TODO: (brian) create a backend class to do this.
# testcenteruser.user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
testcenteruser.user.backend = "%s.%s" % ("TestcenterAuthenticationModule", "TestcenterAuthenticationClass")
# testcenteruser.user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
testcenteruser.user.backend = "%s.%s" % ("TestcenterAuthenticationModule", "TestcenterAuthenticationClass")
login(request, testcenteruser.user)
# And start the test:
return jump_to(request, course_id, location)

View File

@@ -190,7 +190,7 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
}
"""
return {'xml_string' : etree.tostring(xml_object), 'metadata' : xml_object.attrib}
return {'xml_string' : etree.tostring(xml_object), 'metadata' : xml_object.attrib}, []
def definition_to_xml(self, resource_fs):

View File

@@ -696,7 +696,7 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
"""Assumes that xml_object has child k"""
return xml_object.xpath(k)[0]
return {'task_xml': parse_task('task'), 'prompt': parse('prompt'), 'rubric': parse('rubric')}
return {'task_xml': parse_task('task'), 'prompt': parse('prompt'), 'rubric': parse('rubric')}, []
def definition_to_xml(self, resource_fs):

View File

@@ -4,6 +4,7 @@ import logging
from xmodule.x_module import XModule
from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor
from xblock.core import String, Scope
from pkg_resources import resource_string
@@ -34,6 +35,7 @@ class ConditionalModule(XModule):
js_module_name = "Conditional"
css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]}
condition = String(help="Condition for this module", default='', scope=Scope.settings)
def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs):
"""
@@ -44,7 +46,6 @@ class ConditionalModule(XModule):
"""
XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
self.contents = None
self.condition = self.metadata.get('condition', '')
self._get_required_modules()
children = self.get_display_items()
if children:
@@ -128,16 +129,18 @@ class ConditionalDescriptor(SequenceDescriptor):
stores_state = True
has_score = False
required = String(help="List of required xmodule locations, separated by &", default='', scope=Scope.settings)
def __init__(self, *args, **kwargs):
super(ConditionalDescriptor, self).__init__(*args, **kwargs)
required_module_list = [tuple(x.split('/', 1)) for x in self.metadata.get('required', '').split('&')]
required_module_list = [tuple(x.split('/', 1)) for x in self.required.split('&')]
self.required_module_locations = []
for rm in required_module_list:
try:
(tag, name) = rm
except Exception as err:
msg = "Specification of required module in conditional is broken: %s" % self.metadata.get('required')
msg = "Specification of required module in conditional is broken: %s" % self.required
log.warning(msg)
self.system.error_tracker(msg)
continue

View File

@@ -169,6 +169,11 @@ class CourseDescriptor(SequenceDescriptor):
computed_default=lambda c: {'General': {'id': c.location.html_id()}},
)
testcenter_info = Object(help="Dictionary of Test Center info", scope=Scope.settings)
announcement = Date(help="Date this course is announced", scope=Scope.settings)
cohort_config = Object(help="Dictionary defining cohort configuration", scope=Scope.settings)
is_new = Boolean(help="Whether this course should be flagged as new", scope=Scope.settings)
no_grade = Boolean(help="True if this course isn't graded", default=False, scope=Scope.settings)
disable_progress_graph = Boolean(help="True if this course shouldn't display the progress graph", default=False, scope=Scope.settings)
has_children = True
info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
@@ -409,27 +414,12 @@ class CourseDescriptor(SequenceDescriptor):
def lowest_passing_grade(self):
return min(self._grading_policy['GRADE_CUTOFFS'].values())
@property
def tabs(self):
"""
Return the tabs config, as a python object, or None if not specified.
"""
return self.metadata.get('tabs')
@tabs.setter
def tabs(self, value):
self.metadata['tabs'] = value
@property
def show_calculator(self):
return self.metadata.get("show_calculator", None) == "Yes"
@property
def is_cohorted(self):
"""
Return whether the course is cohorted.
"""
config = self.metadata.get("cohort_config")
config = self.cohort_config
if config is None:
return False
@@ -440,7 +430,7 @@ class CourseDescriptor(SequenceDescriptor):
"""
Return list of topic ids defined in course policy.
"""
topics = self.metadata.get("discussion_topics", {})
topics = self.discussion_topics
return [d["id"] for d in topics.values()]
@@ -451,7 +441,7 @@ class CourseDescriptor(SequenceDescriptor):
the empty set. Note that all inline discussions are automatically
cohorted based on the course's is_cohorted setting.
"""
config = self.metadata.get("cohort_config")
config = self.cohort_config
if config is None:
return set()
@@ -460,13 +450,13 @@ class CourseDescriptor(SequenceDescriptor):
@property
def is_new(self):
def is_newish(self):
"""
Returns if the course has been flagged as new in the metadata. If
Returns if the course has been flagged as new. If
there is no flag, return a heuristic value considering the
announcement and the start dates.
"""
flag = self.metadata.get('is_new', None)
flag = self.is_new
if flag is None:
# Use a heuristic if the course has not been flagged
announcement, start, now = self._sorting_dates()
@@ -512,12 +502,10 @@ class CourseDescriptor(SequenceDescriptor):
def to_datetime(timestamp):
return datetime(*timestamp[:6])
def get_date(field):
timetuple = self._try_parse_time(field)
return to_datetime(timetuple) if timetuple else None
announcement = get_date('announcement')
start = get_date('advertised_start') or to_datetime(self.start)
announcement = self.announcement
if announcement is not None:
announcement = to_datetime(announcement)
start = self.advertised_start or to_datetime(self.start)
now = to_datetime(time.gmtime())
return announcement, start, now
@@ -719,7 +707,7 @@ class CourseDescriptor(SequenceDescriptor):
def get_test_center_exam(self, exam_series_code):
exams = [exam for exam in self.test_center_exams if exam.exam_series_code == exam_series_code]
return exams[0] if len(exams) == 1 else None
@property
def title(self):
return self.display_name

View File

@@ -177,7 +177,7 @@ class GraphicalSliderToolDescriptor(MakoModuleDescriptor, XmlDescriptor):
return {
'render': parse('render'),
'configuration': parse('configuration')
}
}, []
def definition_to_xml(self, resource_fs):
'''Return an xml element representing this definition.'''

View File

@@ -456,6 +456,9 @@ class XMLModuleStore(ModuleStoreBase):
def _load_extra_content(self, system, course_descriptor, category, path, course_dir):
for filepath in glob.glob(path / '*'):
if not os.path.isfile(filepath):
continue
with open(filepath) as f:
try:
html = f.read().decode('utf-8')

View File

@@ -199,16 +199,13 @@ def import_from_xml(store, data_dir, course_dirs=None,
course_items = []
for course_id in module_store.modules.keys():
course_data_path = None
course_location = None
# Import course modules first, because importing some of the children requires the course to exist
for module in module_store.modules[course_id].itervalues():
if module.category == 'course':
import_course_from_xml(
store,
static_content_store,
course_data_path,
data_dir / module.data_dir,
module,
target_location_namespace,
verbose=verbose
@@ -220,7 +217,7 @@ def import_from_xml(store, data_dir, course_dirs=None,
import_module_from_xml(
store,
static_content_store,
course_data_path,
data_dir / module.data_dir,
module,
target_location_namespace,
verbose=verbose

View File

@@ -20,7 +20,6 @@ import capa.xqueue_interface as xqueue_interface
from pkg_resources import resource_string
from .capa_module import only_one, ComplexEncoder
from .editing_module import EditingDescriptor
from .html_checker import check_html
from progress import Progress
@@ -656,7 +655,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
"""Assumes that xml_object has child k"""
return xml_object.xpath(k)[0]
return {'oeparam': parse('openendedparam'), }
return {'oeparam': parse('openendedparam')}, []
def definition_to_xml(self, resource_fs):

View File

@@ -32,6 +32,7 @@ from .stringify import stringify_children
from .x_module import XModule
from .xml_module import XmlDescriptor
from xmodule.modulestore import Location
from xblock.core import Scope, Object, Integer, Boolean, String
from peer_grading_service import peer_grading_service, GradingServiceError
@@ -56,32 +57,26 @@ class PeerGradingModule(XModule):
css = {'scss': [resource_string(__name__, 'css/combinedopenended/display.scss')]}
def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
student_data_for_location = Object(scope=Scope.student_state)
max_grade = Integer(default=MAX_SCORE, scope=Scope.student_state)
use_for_single_location = Boolean(default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
is_graded = Boolean(default=IS_GRADED, scope=Scope.settings)
link_to_location = String(default=LINK_TO_LOCATION, scope=Scope.settings)
# Load instance state
if instance_state is not None:
instance_state = json.loads(instance_state)
else:
instance_state = {}
def __init__(self, *args, **kwargs):
super(PeerGradingModule, self).__init__(*args, **kwargs)
#We need to set the location here so the child modules can use it
system.set('location', location)
self.system = system
self.system.set('location', self.location)
self.peer_gs = peer_grading_service(self.system)
self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
if isinstance(self.use_for_single_location, basestring):
self.use_for_single_location = (self.use_for_single_location in TRUE_DICT)
self.is_graded = self.metadata.get('is_graded', IS_GRADED)
if isinstance(self.is_graded, basestring):
self.is_graded = (self.is_graded in TRUE_DICT)
self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION)
if self.use_for_single_location == True:
if self.use_for_single_location:
#This will raise an exception if the location is invalid
link_to_location_object = Location(self.link_to_location)
@@ -89,8 +84,6 @@ class PeerGradingModule(XModule):
if not self.ajax_url.endswith("/"):
self.ajax_url = self.ajax_url + "/"
self.student_data_for_location = instance_state.get('student_data_for_location', {})
self.max_grade = instance_state.get('max_grade', MAX_SCORE)
if not isinstance(self.max_grade, (int, long)):
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack
self.max_grade = int(self.max_grade)
@@ -521,7 +514,7 @@ class PeerGradingDescriptor(XmlDescriptor, EditingDescriptor):
"""Assumes that xml_object has child k"""
return xml_object.xpath(k)[0]
return {}
return {}, []
def definition_to_xml(self, resource_fs):

View File

@@ -58,10 +58,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
max_score = Integer(scope=Scope.settings, default=MAX_SCORE)
max_score = Integer(scope=Scope.settings, default=openendedchild.MAX_SCORE)
max_attempts = Integer(scope=Scope.settings, default=openendedchild.MAX_ATTEMPTS)
attempts = Integer(scope=Scope.student_state, default=0)
max_attempts = Integer(scope=Scope.settings, default=MAX_ATTEMPTS)
rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content)
submitmessage = String(scope=Scope.content)
@@ -318,9 +318,9 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
max_score = Integer(scope=Scope.settings, default=MAX_SCORE)
max_score = Integer(scope=Scope.settings, default=openendedchild.MAX_SCORE)
max_attempts = Integer(scope=Scope.settings, default=MAX_ATTEMPTS)
max_attempts = Integer(scope=Scope.settings, default=openendedchild.MAX_ATTEMPTS)
rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content)
submitmessage = String(scope=Scope.content)

View File

@@ -94,26 +94,26 @@ class IsNewCourseTestCase(unittest.TestCase):
@patch('xmodule.course_module.time.gmtime')
def test_is_new(self, gmtime_mock):
def test_is_newish(self, gmtime_mock):
gmtime_mock.return_value = NOW
descriptor = self.get_dummy_course(start='2012-12-02T12:00', is_new=True)
assert(descriptor.is_new is True)
assert(descriptor.is_newish is True)
descriptor = self.get_dummy_course(start='2013-02-02T12:00', is_new=False)
assert(descriptor.is_new is False)
descriptor = self.get_dummy_course(start='2013-02-02T12:00', is_new=True)
assert(descriptor.is_new is True)
assert(descriptor.is_newish is True)
descriptor = self.get_dummy_course(start='2013-01-15T12:00')
assert(descriptor.is_new is True)
assert(descriptor.is_newish is True)
descriptor = self.get_dummy_course(start='2013-03-00T12:00')
assert(descriptor.is_new is True)
assert(descriptor.is_newish is True)
descriptor = self.get_dummy_course(start='2012-10-15T12:00')
assert(descriptor.is_new is False)
assert(descriptor.is_newish is False)
descriptor = self.get_dummy_course(start='2012-12-31T12:00')
assert(descriptor.is_new is True)
assert(descriptor.is_newish is True)