From 2011861444a00e2d2bf7dcfc81d80d17e245d665 Mon Sep 17 00:00:00 2001
From: Calen Pennington <calen.pennington@gmail.com>
Date: Wed, 27 Jun 2012 16:15:40 -0400
Subject: [PATCH] Get capa problems to display from a keystore

---
 common/lib/xmodule/capa_module.py             | 130 ++++++++----------
 common/lib/xmodule/setup.py                   |   3 +-
 common/lib/xmodule/vertical_module.py         |   5 +-
 lms/djangoapps/courseware/module_render.py    | 109 +++------------
 lms/lib/dogfood/views.py                      |   2 +-
 lms/static/coffee/src/courseware.coffee       |   4 +-
 lms/static/coffee/src/modules/problem.coffee  |  16 +--
 lms/static/coffee/src/modules/sequence.coffee |   2 +-
 lms/templates/problem_ajax.html               |   2 +-
 lms/urls.py                                   |   2 +-
 10 files changed, 101 insertions(+), 174 deletions(-)

diff --git a/common/lib/xmodule/capa_module.py b/common/lib/xmodule/capa_module.py
index 5047b948328..0563017ff2e 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 = '<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.format(
-                id=self.item_id, ajax_url=self.ajax_url) + html + "</div>"
+                id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "</div>"
 
         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><text><font color="red" size="+2">Problem file %s is missing</font></text></problem>' % 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 = '<p>%s</p>' % msg.replace('<','&lt;')
-                msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<','&lt;')
+            if self.system.DEBUG:
+                msg = '<p>%s</p>' % msg.replace('<', '&lt;')
+                msg += '<p><pre>%s</pre></p>' % traceback.format_exc().replace('<', '&lt;')
                 # create a dummy problem with error message instead of failing
-                fp = StringIO.StringIO('<problem><text><font color="red" size="+2">Problem file %s has an error:</font>%s</text></problem>' % (self.filename,msg))
+                fp = StringIO.StringIO('<problem><text><font color="red" size="+2">Problem file %s has an error:</font>%s</text></problem>' % (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 3e3e33805f7..a9f4e1f4dc9 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 6153aff324a..6008eb42261 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 d05bdcefab8..d8ebb82adb6 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()
-
-    if shared_state is not None:
-        shared_state.state = instance.get_shared_state()
-        if shared_state.state != old_shared_state:
-            shared_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_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 a91314d228b..ba8601cc20e 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 de232e05e40..4e57d13194c 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 f29c9eb72bb..eb2c057beff 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 a4a80e34078..2c979f08539 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 78b85df3c1e..6330edfac05 100644
--- a/lms/templates/problem_ajax.html
+++ b/lms/templates/problem_ajax.html
@@ -1 +1 @@
-<section id="problem_${id}" class="problems-wrapper" data-url="${ajax_url}"></section>
+<section id="problem_${element_id}" class="problems-wrapper" problem-id="${id}" data-url="${ajax_url}"></section>
diff --git a/lms/urls.py b/lms/urls.py
index 313be62c516..e43c949643c 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -57,7 +57,7 @@ if settings.COURSEWARE_ENABLED:
         url(r'^courseware/(?P<course>[^/]*)/$', 'courseware.views.index', name="courseware_course"),
         url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'),
         url(r'^section/(?P<section>[^/]*)/$', 'courseware.views.render_section'),
-        url(r'^modx/(?P<module>[^/]*)/(?P<id>[^/]*)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
+        url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
         url(r'^profile$', 'courseware.views.profile'),
         url(r'^profile/(?P<student_id>[^/]*)/$', 'courseware.views.profile'),
         url(r'^change_setting$', 'student.views.change_setting'),
-- 
GitLab