diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py
index 5047b94832..0563017ff2 100644
--- a/common/lib/xmodule/capa_module.py
+++ b/common/lib/xmodule/capa_module.py
@@ -10,8 +10,8 @@ import StringIO
from datetime import timedelta
from lxml import etree
-from x_module import XModule
-from mako_module import MakoModuleDescriptor
+from xmodule.x_module import XModule
+from xmodule.raw_module import RawDescriptor
from progress import Progress
from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError
@@ -64,37 +64,25 @@ class ComplexEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, obj)
-class CapaModuleDescriptor(MakoModuleDescriptor):
- """
- Module implementing problems in the LON-CAPA format,
- as implemented by capa.capa_problem
- """
-
- mako_template = 'widgets/problem-edit.html'
-
-
-
-class Module(XModule):
+class CapaModule(XModule):
''' Interface between capa_problem and x_module. Originally a hack
meant to be refactored out, but it seems to be serving a useful
prupose now. We can e.g .destroy and create the capa_problem on a
reset.
'''
+ icon_class = 'problem'
def get_instance_state(self):
state = self.lcp.get_state()
state['attempts'] = self.attempts
return json.dumps(state)
-
def get_score(self):
return self.lcp.get_score()
-
def max_score(self):
return self.lcp.get_max_score()
-
def get_progress(self):
''' For now, just return score / max_score
'''
@@ -105,14 +93,13 @@ class Module(XModule):
return Progress(score, total)
return None
-
def get_html(self):
return self.system.render_template('problem_ajax.html', {
- 'id': self.item_id,
- 'ajax_url': self.ajax_url,
+ 'element_id': self.location.html_id(),
+ 'id': self.id,
+ 'ajax_url': self.system.ajax_url,
})
-
def get_problem_html(self, encapsulate=True):
'''Return html for the problem. Adds check, reset, save buttons
as necessary based on the problem config and state.'''
@@ -165,12 +152,12 @@ class Module(XModule):
explain = False
context = {'problem': content,
- 'id': self.item_id,
+ 'id': self.id,
'check_button': check_button,
'reset_button': reset_button,
'save_button': save_button,
'answer_available': self.answer_available(),
- 'ajax_url': self.ajax_url,
+ 'ajax_url': self.system.ajax_url,
'attempts_used': self.attempts,
'attempts_allowed': self.max_attempts,
'explain': explain,
@@ -180,17 +167,17 @@ class Module(XModule):
html = self.system.render_template('problem.html', context)
if encapsulate:
html = '
'.format(
- id=self.item_id, ajax_url=self.ajax_url) + html + "
"
+ id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + ""
return html
- def __init__(self, system, xml, item_id, instance_state=None, shared_state=None):
- XModule.__init__(self, system, xml, item_id, instance_state, shared_state)
+ def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
+ XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
self.attempts = 0
self.max_attempts = None
- dom2 = etree.fromstring(xml)
+ dom2 = etree.fromstring(definition['data'])
self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'),
default="closed")
@@ -205,7 +192,7 @@ class Module(XModule):
self.display_due_date = None
grace_period_string = only_one(dom2.xpath('/problem/@graceperiod'))
- if len(grace_period_string) >0 and self.display_due_date:
+ if len(grace_period_string) > 0 and self.display_due_date:
self.grace_period = parse_timedelta(grace_period_string)
self.close_date = self.display_due_date + self.grace_period
#log.debug("Then parsed " + grace_period_string + " to closing date" + str(self.close_date))
@@ -240,9 +227,9 @@ class Module(XModule):
self.attempts = instance_state['attempts']
# TODO: Should be: self.filename=only_one(dom2.xpath('/problem/@filename'))
- self.filename= "problems/"+only_one(dom2.xpath('/problem/@filename'))+".xml"
- self.name=only_one(dom2.xpath('/problem/@name'))
- self.weight=only_one(dom2.xpath('/problem/@weight'))
+ self.filename = "problems/" + only_one(dom2.xpath('/problem/@filename')) + ".xml"
+ self.name = only_one(dom2.xpath('/problem/@name'))
+ self.weight = only_one(dom2.xpath('/problem/@weight'))
if self.rerandomize == 'never':
seed = 1
elif self.rerandomize == "per_student" and hasattr(system, 'id'):
@@ -250,27 +237,27 @@ class Module(XModule):
else:
seed = None
try:
- fp = self.filestore.open(self.filename)
- except Exception,err:
- log.exception('[courseware.capa.capa_module.Module.init] error %s: cannot open file %s' % (err,self.filename))
- if self.DEBUG:
+ fp = self.system.filestore.open(self.filename)
+ except Exception:
+ log.exception('cannot open file %s' % self.filename)
+ if self.system.DEBUG:
# create a dummy problem instead of failing
fp = StringIO.StringIO('Problem file %s is missing' % self.filename)
fp.name = "StringIO"
else:
raise
try:
- self.lcp=LoncapaProblem(fp, self.item_id, instance_state, seed = seed, system=self.system)
- except Exception,err:
- msg = '[courseware.capa.capa_module.Module.init] error %s: cannot create LoncapaProblem %s' % (err,self.filename)
+ self.lcp = LoncapaProblem(fp, self.id, instance_state, seed=seed, system=self.system)
+ except Exception:
+ msg = 'cannot create LoncapaProblem %s' % self.filename
log.exception(msg)
- if self.DEBUG:
- msg = '%s
' % msg.replace('<','<')
- msg += '%s
' % traceback.format_exc().replace('<','<')
+ if self.system.DEBUG:
+ msg = '%s
' % msg.replace('<', '<')
+ msg += '%s
' % traceback.format_exc().replace('<', '<')
# create a dummy problem with error message instead of failing
- fp = StringIO.StringIO('Problem file %s has an error:%s' % (self.filename,msg))
+ fp = StringIO.StringIO('Problem file %s has an error:%s' % (self.filename, msg))
fp.name = "StringIO"
- self.lcp=LoncapaProblem(fp, self.item_id, instance_state, seed = seed, system=self.system)
+ self.lcp = LoncapaProblem(fp, self.id, instance_state, seed=seed, system=self.system)
else:
raise
@@ -299,8 +286,8 @@ class Module(XModule):
d = handlers[dispatch](get)
after = self.get_progress()
d.update({
- 'progress_changed' : after != before,
- 'progress_status' : Progress.to_js_status_str(after),
+ 'progress_changed': after != before,
+ 'progress_status': Progress.to_js_status_str(after),
})
return json.dumps(d, cls=ComplexEncoder)
@@ -313,7 +300,6 @@ class Module(XModule):
return False
-
def answer_available(self):
''' Is the user allowed to see an answer?
'''
@@ -334,7 +320,8 @@ class Module(XModule):
if self.show_answer == 'always':
return True
- raise self.system.exception404 #TODO: Not 404
+ #TODO: Not 404
+ raise self.system.exception404
def get_answer(self, get):
'''
@@ -348,8 +335,7 @@ class Module(XModule):
raise self.system.exception404
else:
answers = self.lcp.get_question_answers()
- return {'answers' : answers}
-
+ return {'answers': answers}
# Figure out if we should move these to capa_problem?
def get_problem(self, get):
@@ -358,8 +344,8 @@ class Module(XModule):
Used if we want to reconfirm we have the right thing e.g. after
several AJAX calls.
- '''
- return {'html' : self.get_problem_html(encapsulate=False)}
+ '''
+ return {'html': self.get_problem_html(encapsulate=False)}
@staticmethod
def make_dict_of_responses(get):
@@ -409,18 +395,16 @@ class Module(XModule):
correct_map = self.lcp.grade_answers(answers)
except StudentInputError as inst:
# TODO (vshnayder): why is this line here?
- self.lcp = LoncapaProblem(self.filestore.open(self.filename),
+ self.lcp = LoncapaProblem(self.system.filestore.open(self.filename),
id=lcp_id, state=old_state, system=self.system)
traceback.print_exc()
return {'success': inst.message}
except:
# TODO: why is this line here?
- self.lcp = LoncapaProblem(self.filestore.open(self.filename),
+ self.lcp = LoncapaProblem(self.system.filestore.open(self.filename),
id=lcp_id, state=old_state, system=self.system)
traceback.print_exc()
- raise Exception,"error in capa_module"
- # TODO: Dead code... is this a bug, or just old?
- return {'success':'Unknown Error'}
+ raise Exception("error in capa_module")
self.attempts = self.attempts + 1
self.lcp.done = True
@@ -431,21 +415,18 @@ class Module(XModule):
if not correct_map.is_correct(answer_id):
success = 'incorrect'
- event_info['correct_map'] = correct_map.get_dict() # log this in the tracker
+ # log this in the tracker
+ event_info['correct_map'] = correct_map.get_dict()
event_info['success'] = success
self.tracker('save_problem_check', event_info)
- try:
- html = self.get_problem_html(encapsulate=False) # render problem into HTML
- except Exception,err:
- log.error('failed to generate html')
- raise
+ # render problem into HTML
+ html = self.get_problem_html(encapsulate=False)
return {'success': success,
'contents': html,
}
-
def save_problem(self, get):
'''
Save the passed in answers.
@@ -471,8 +452,8 @@ class Module(XModule):
if self.lcp.done and self.rerandomize == "always":
event_info['failure'] = 'done'
self.tracker('save_problem_fail', event_info)
- return {'success' : False,
- 'error' : "Problem needs to be reset prior to save."}
+ return {'success': False,
+ 'error': "Problem needs to be reset prior to save."}
self.lcp.student_answers = answers
@@ -485,7 +466,7 @@ class Module(XModule):
and causes problem to rerender itself.
Returns problem html as { 'html' : html-string }.
- '''
+ '''
event_info = dict()
event_info['old_state'] = self.lcp.get_state()
event_info['filename'] = self.filename
@@ -503,12 +484,21 @@ class Module(XModule):
self.lcp.do_reset()
if self.rerandomize == "always":
# reset random number generator seed (note the self.lcp.get_state() in next line)
- self.lcp.seed=None
-
- self.lcp = LoncapaProblem(self.filestore.open(self.filename),
- self.item_id, self.lcp.get_state(), system=self.system)
+ self.lcp.seed = None
+
+ self.lcp = LoncapaProblem(self.system.filestore.open(self.filename),
+ self.id, self.lcp.get_state(), system=self.system)
event_info['new_state'] = self.lcp.get_state()
self.tracker('reset_problem', event_info)
- return {'html' : self.get_problem_html(encapsulate=False)}
+ return {'html': self.get_problem_html(encapsulate=False)}
+
+
+class CapaDescriptor(RawDescriptor):
+ """
+ Module implementing problems in the LON-CAPA format,
+ as implemented by capa.capa_problem
+ """
+
+ module_class = CapaModule
diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py
index 3e3e33805f..a9f4e1f4dc 100644
--- a/common/lib/xmodule/setup.py
+++ b/common/lib/xmodule/setup.py
@@ -19,9 +19,10 @@ setup(
"section = xmodule.translation_module:SemanticSectionDescriptor",
"sequential = xmodule.seq_module:SequenceDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor",
+ "problem = xmodule.capa_module:CapaDescriptor",
"problemset = xmodule.seq_module:SequenceDescriptor",
- "videosequence = xmodule.seq_module:SequenceDescriptor",
"video = xmodule.video_module:VideoDescriptor",
+ "videosequence = xmodule.seq_module:SequenceDescriptor",
]
}
)
diff --git a/common/lib/xmodule/vertical_module.py b/common/lib/xmodule/vertical_module.py
index 6153aff324..6008eb4226 100644
--- a/common/lib/xmodule/vertical_module.py
+++ b/common/lib/xmodule/vertical_module.py
@@ -10,6 +10,9 @@ class_priority = ['video', 'problem']
class VerticalModule(XModule):
''' Layout module for laying out submodules vertically.'''
def get_html(self):
+ if self.contents is None:
+ self.contents = [child.get_html() for child in self.get_display_items()]
+
return self.system.render_template('vert_module.html', {
'items': self.contents
})
@@ -31,7 +34,7 @@ class VerticalModule(XModule):
def __init__(self, system, location, definition, instance_state=None, shared_state=None, **kwargs):
XModule.__init__(self, system, location, definition, instance_state, shared_state, **kwargs)
- self.contents = [child.get_html() for child in self.get_display_items()]
+ self.contents = None
class VerticalDescriptor(SequenceDescriptor):
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index d05bdcefab..d8ebb82adb 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -60,13 +60,7 @@ class I4xSystem(object):
'''
self.ajax_url = ajax_url
self.track_function = track_function
- if not filestore:
- self.filestore = OSFS(settings.DATA_DIR)
- else:
- self.filestore = filestore
- if settings.DEBUG:
- log.info("[courseware.module_render.I4xSystem] filestore path = %s",
- filestore)
+ self.filestore = filestore
self.get_module = get_module
self.render_function = render_function
self.render_template = render_template
@@ -241,7 +235,7 @@ def get_module(user, request, location, student_module_cache, position=None):
shared_state = shared_module.state if shared_module is not None else None
# Setup system context for module instance
- ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.type + '/' + descriptor.url + '/'
+ ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.url + '/'
def _get_module(location):
(module, _, _, _) = get_module(user, request, location, student_module_cache, position)
@@ -330,94 +324,33 @@ def render_x_module(user, request, module_xml, student_module_cache, position=No
return context
-def modx_dispatch(request, module=None, dispatch=None, id=None):
+def modx_dispatch(request, dispatch=None, id=None):
''' Generic view for extensions. This is where AJAX calls go.
Arguments:
- request -- the django request.
- - module -- the type of the module, as used in the course configuration xml.
- e.g. 'problem', 'video', etc
- dispatch -- the command string to pass through to the module's handle_ajax call
(e.g. 'problem_reset'). If this string contains '?', only pass
through the part before the first '?'.
- - id -- the module id. Used to look up the student module.
- e.g. filenamexformularesponse
+ - id -- the module id. Used to look up the XModule instance
'''
# ''' (fix emacs broken parsing)
- if not request.user.is_authenticated():
- return redirect('/')
-
- # python concats adjacent strings
- error_msg = ("We're sorry, this module is temporarily unavailable. "
- "Our staff is working to fix it as soon as possible")
# If there are arguments, get rid of them
dispatch, _, _ = dispatch.partition('?')
- ajax_url = '{root}/modx/{module}/{id}'.format(root=settings.MITX_ROOT_URL,
- module=module, id=id)
- coursename = multicourse_settings.get_coursename_from_request(request)
- if coursename and settings.ENABLE_MULTICOURSE:
- xp = multicourse_settings.get_course_xmlpath(coursename)
- data_root = settings.DATA_DIR + xp
- else:
- data_root = settings.DATA_DIR
+ student_module_cache = StudentModuleCache(request.user, keystore().get_item(id))
+ instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
- # Grab the XML corresponding to the request from course.xml
- try:
- xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
- except:
- log.exception(
- "Unable to load module during ajax call. module=%s, dispatch=%s, id=%s",
- module, dispatch, id)
- if accepts(request, 'text/html'):
- return render_to_response("module-error.html", {})
- else:
- response = HttpResponse(json.dumps({'success': error_msg}))
- return response
-
- module_xml = etree.fromstring(xml)
- student_module_cache = StudentModuleCache(request.user, module_xml)
- (instance, instance_state, shared_state, module_type) = get_module(
- request.user, request, module_xml,
- student_module_cache, None)
-
- if instance_state is None:
- log.debug("Couldn't find module '%s' for user '%s' and id '%s'",
- module, request.user, id)
+ if instance_module is None:
+ log.debug("Couldn't find module '%s' for user '%s'",
+ id, request.user)
raise Http404
- oldgrade = instance_state.grade
- old_instance_state = instance_state.state
- old_shared_state = shared_state.state if shared_state is not None else None
-
- module_from_xml = make_module_from_xml_fn(
- request.user, request, student_module_cache, None)
-
- # Create the module
- system = I4xSystem(track_function=make_track_function(request),
- render_function=None,
- module_from_xml=module_from_xml,
- render_template=render_to_string,
- ajax_url=ajax_url,
- request=request,
- filestore=OSFS(data_root),
- )
-
- try:
- module_class = xmodule.get_module_class(module)
- instance = module_class(
- system, xml, id,
- instance_state=old_instance_state,
- shared_state=old_shared_state)
- except:
- log.exception("Unable to load module instance during ajax call")
- if accepts(request, 'text/html'):
- return render_to_response("module-error.html", {})
- else:
- response = HttpResponse(json.dumps({'success': error_msg}))
- return response
+ oldgrade = instance_module.grade
+ old_instance_state = instance_module.state
+ old_shared_state = shared_module.state if shared_module is not None else None
# Let the module handle the AJAX
try:
@@ -427,16 +360,16 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
raise
# Save the state back to the database
- instance_state.state = instance.get_instance_state()
+ instance_module.state = instance.get_instance_state()
if instance.get_score():
- instance_state.grade = instance.get_score()['score']
- if instance_state.grade != oldgrade or instance_state.state != old_instance_state:
- instance_state.save()
+ instance_module.grade = instance.get_score()['score']
+ if instance_module.grade != oldgrade or instance_module.state != old_instance_state:
+ instance_module.save()
- if shared_state is not None:
- shared_state.state = instance.get_shared_state()
- if shared_state.state != old_shared_state:
- shared_state.save()
+ if shared_module is not None:
+ shared_module.state = instance.get_shared_state()
+ if shared_module.state != old_shared_state:
+ shared_module.save()
# Return whatever the module wanted to return to the client/caller
return HttpResponse(ajax_return)
diff --git a/lms/lib/dogfood/views.py b/lms/lib/dogfood/views.py
index a91314d228..ba8601cc20 100644
--- a/lms/lib/dogfood/views.py
+++ b/lms/lib/dogfood/views.py
@@ -174,7 +174,7 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
module = 'problem'
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
- ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/'
+ ajax_url = settings.MITX_ROOT_URL + '/modx/'+id+'/'
# Create the module (instance of capa_module.Module)
system = I4xSystem(track_function = make_track_function(request),
diff --git a/lms/static/coffee/src/courseware.coffee b/lms/static/coffee/src/courseware.coffee
index de232e05e4..4e57d13194 100644
--- a/lms/static/coffee/src/courseware.coffee
+++ b/lms/static/coffee/src/courseware.coffee
@@ -20,8 +20,8 @@ class @Courseware
id = $(this).attr('id').replace(/video_/, '')
new Video id, $(this).data('streams')
$('.course-content .problems-wrapper').each ->
- id = $(this).attr('id').replace(/problem_/, '')
- new Problem id, $(this).data('url')
+ id = $(this).attr('problem-id')
+ new Problem id, $(this).attr('id'), $(this).data('url')
$('.course-content .histogram').each ->
id = $(this).attr('id').replace(/histogram_/, '')
new Histogram id, $(this).data('histogram')
diff --git a/lms/static/coffee/src/modules/problem.coffee b/lms/static/coffee/src/modules/problem.coffee
index f29c9eb72b..eb2c057bef 100644
--- a/lms/static/coffee/src/modules/problem.coffee
+++ b/lms/static/coffee/src/modules/problem.coffee
@@ -1,6 +1,6 @@
class @Problem
- constructor: (@id, url) ->
- @element = $("#problem_#{id}")
+ constructor: (@id, @element_id, url) ->
+ @element = $("##{element_id}")
@render()
$: (selector) ->
@@ -26,13 +26,13 @@ class @Problem
@element.html(content)
@bind()
else
- $.postWithPrefix "/modx/problem/#{@id}/problem_get", (response) =>
+ $.postWithPrefix "/modx/#{@id}/problem_get", (response) =>
@element.html(response.html)
@bind()
check: =>
Logger.log 'problem_check', @answers
- $.postWithPrefix "/modx/problem/#{@id}/problem_check", @answers, (response) =>
+ $.postWithPrefix "/modx/#{@id}/problem_check", @answers, (response) =>
switch response.success
when 'incorrect', 'correct'
@render(response.contents)
@@ -42,14 +42,14 @@ class @Problem
reset: =>
Logger.log 'problem_reset', @answers
- $.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (response) =>
+ $.postWithPrefix "/modx/#{@id}/problem_reset", id: @id, (response) =>
@render(response.html)
@updateProgress response
show: =>
if !@element.hasClass 'showed'
Logger.log 'problem_show', problem: @id
- $.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) =>
+ $.postWithPrefix "/modx/#{@id}/problem_show", (response) =>
answers = response.answers
$.each answers, (key, value) =>
if $.isArray(value)
@@ -69,7 +69,7 @@ class @Problem
save: =>
Logger.log 'problem_save', @answers
- $.postWithPrefix "/modx/problem/#{@id}/problem_save", @answers, (response) =>
+ $.postWithPrefix "/modx/#{@id}/problem_save", @answers, (response) =>
if response.success
alert 'Saved'
@updateProgress response
@@ -94,4 +94,4 @@ class @Problem
element.schematic.update_value()
@$(".CodeMirror").each (index, element) ->
element.CodeMirror.save() if element.CodeMirror.save
- @answers = @$("[id^=input_#{@id}_]").serialize()
+ @answers = @$("[id^=input_#{@element_id}_]").serialize()
diff --git a/lms/static/coffee/src/modules/sequence.coffee b/lms/static/coffee/src/modules/sequence.coffee
index a4a80e3407..2c979f0853 100644
--- a/lms/static/coffee/src/modules/sequence.coffee
+++ b/lms/static/coffee/src/modules/sequence.coffee
@@ -88,7 +88,7 @@ class @Sequence
if @position != new_position
if @position != undefined
@mark_visited @position
- $.postWithPrefix "/modx/#{@tag}/#{@id}/goto_position", position: new_position
+ $.postWithPrefix "/modx/#{@id}/goto_position", position: new_position
@mark_active new_position
@$('#seq_content').html @elements[new_position - 1].content
diff --git a/lms/templates/problem_ajax.html b/lms/templates/problem_ajax.html
index 78b85df3c1..6330edfac0 100644
--- a/lms/templates/problem_ajax.html
+++ b/lms/templates/problem_ajax.html
@@ -1 +1 @@
-
+
diff --git a/lms/urls.py b/lms/urls.py
index 313be62c51..e43c949643 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -57,7 +57,7 @@ if settings.COURSEWARE_ENABLED:
url(r'^courseware/(?P[^/]*)/$', 'courseware.views.index', name="courseware_course"),
url(r'^jumpto/(?P[^/]+)/$', 'courseware.views.jump_to'),
url(r'^section/(?P[^/]*)/$', 'courseware.views.render_section'),
- url(r'^modx/(?P[^/]*)/(?P[^/]*)/(?P[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
+ url(r'^modx/(?P.*?)/(?P[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
url(r'^profile$', 'courseware.views.profile'),
url(r'^profile/(?P[^/]*)/$', 'courseware.views.profile'),
url(r'^change_setting$', 'student.views.change_setting'),