diff --git a/djangoapps/courseware/module_render.py b/djangoapps/courseware/module_render.py index ba550fe935..de8364b752 100644 --- a/djangoapps/courseware/module_render.py +++ b/djangoapps/courseware/module_render.py @@ -18,6 +18,7 @@ from models import StudentModule from multicourse import multicourse_settings import courseware.modules +import courseware.content_parser as content_parser log = logging.getLogger("mitx.courseware") @@ -79,9 +80,8 @@ def grade_histogram(module_id): return [] return grades -def render_x_module(user, request, xml_module, module_object_preload): - ''' Generic module for extensions. This renders to HTML. ''' - # Check if problem has an instance in DB + +def get_module(user, request, xml_module, module_object_preload): module_type=xml_module.tag module_class=courseware.modules.get_module_class(module_type) module_id=xml_module.get('id') #module_class.id_attribute) or "" @@ -110,7 +110,7 @@ def render_x_module(user, request, xml_module, module_object_preload): ajax_url = settings.MITX_ROOT_URL + '/modx/'+module_type+'/'+module_id+'/' system = I4xSystem(track_function = make_track_function(request), - render_function = lambda x: render_module(user, request, x, module_object_preload), + render_function = lambda x: render_x_module(user, request, x, module_object_preload), ajax_url = ajax_url, filestore = OSFS(data_root), ) @@ -129,6 +129,15 @@ def render_x_module(user, request, xml_module, module_object_preload): smod.save() module_object_preload.append(smod) + return (instance, smod, module_type) + +def render_x_module(user, request, xml_module, module_object_preload): + ''' Generic module for extensions. This renders to HTML. ''' + if xml_module==None : + return {"content":""} + + (instance, smod, module_type) = get_module(user, request, xml_module, module_object_preload) + # Grab content content = instance.get_html() @@ -146,8 +155,76 @@ def render_x_module(user, request, xml_module, module_object_preload): return content -def render_module(user, request, module, module_object_preload): - ''' Generic dispatch for internal modules. ''' - if module==None : - return {"content":""} - return render_x_module(user, request, module, module_object_preload) + +def modx_dispatch(request, module=None, dispatch=None, id=None): + ''' Generic view for extensions. ''' + if not request.user.is_authenticated(): + return redirect('/') + + # Grab the student information for the module from the database + s = StudentModule.objects.filter(student=request.user, + module_id=id) + #s = StudentModule.get_with_caching(request.user, id) + if len(s) == 0 or s is None: + log.debug("Couldnt find module for user and id " + str(module) + " " + str(request.user) + " "+ str(id)) + raise Http404 + s = s[0] + + oldgrade = s.grade + oldstate = s.state + + dispatch=dispatch.split('?')[0] + + ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/' + + # get coursename if stored + coursename = multicourse_settings.get_coursename_from_request(request) + + if coursename and settings.ENABLE_MULTICOURSE: + xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course + data_root = settings.DATA_DIR + xp + else: + data_root = settings.DATA_DIR + + # 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") + if accepts(request, 'text/html'): + return render_to_response("module-error.html", {}) + else: + response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) + return response + + # Create the module + system = I4xSystem(track_function = make_track_function(request), + render_function = None, + ajax_url = ajax_url, + filestore = OSFS(data_root), + ) + + try: + instance=courseware.modules.get_module_class(module)(system, + xml, + id, + state=oldstate) + 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': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) + return response + + # Let the module handle the AJAX + ajax_return=instance.handle_ajax(dispatch, request.POST) + # Save the state back to the database + s.state=instance.get_state() + if instance.get_score(): + s.grade=instance.get_score()['score'] + if s.grade != oldgrade or s.state != oldstate: + s.save() + # Return whatever the module wanted to return to the client/caller + return HttpResponse(ajax_return) + diff --git a/djangoapps/courseware/modules/capa_module.py b/djangoapps/courseware/modules/capa_module.py index 25874c32b5..ba02bd854e 100644 --- a/djangoapps/courseware/modules/capa_module.py +++ b/djangoapps/courseware/modules/capa_module.py @@ -18,7 +18,7 @@ from lxml import etree ## TODO: Abstract out from Django from mitxmako.shortcuts import render_to_string -from x_module import XModule +from x_module import XModule, XModuleDescriptor from courseware.capa.capa_problem import LoncapaProblem, StudentInputError import courseware.content_parser as content_parser from multicourse import multicourse_settings @@ -31,6 +31,9 @@ class ComplexEncoder(json.JSONEncoder): return "{real:.7g}{imag:+.7g}*j".format(real = obj.real,imag = obj.imag) return json.JSONEncoder.default(self, obj) +class ModuleDescriptor(XModuleDescriptor): + pass + class Module(XModule): ''' Interface between capa_problem and x_module. Originally a hack meant to be refactored out, but it seems to be serving a useful diff --git a/djangoapps/courseware/modules/html_module.py b/djangoapps/courseware/modules/html_module.py index 77bcbb4bbc..bf60174e18 100644 --- a/djangoapps/courseware/modules/html_module.py +++ b/djangoapps/courseware/modules/html_module.py @@ -2,9 +2,12 @@ import json from mitxmako.shortcuts import render_to_response, render_to_string -from x_module import XModule +from x_module import XModule, XModuleDescriptor from lxml import etree +class ModuleDescriptor(XModuleDescriptor): + pass + class Module(XModule): id_attribute = 'filename' diff --git a/djangoapps/courseware/modules/schematic_module.py b/djangoapps/courseware/modules/schematic_module.py index 5fef265e01..a6459b5e6e 100644 --- a/djangoapps/courseware/modules/schematic_module.py +++ b/djangoapps/courseware/modules/schematic_module.py @@ -4,7 +4,10 @@ import json from django.conf import settings from mitxmako.shortcuts import render_to_response, render_to_string -from x_module import XModule +from x_module import XModule, XModuleDescriptor + +class ModuleDescriptor(XModuleDescriptor): + pass class Module(XModule): id_attribute = 'id' diff --git a/djangoapps/courseware/modules/seq_module.py b/djangoapps/courseware/modules/seq_module.py index deec191173..3604fa0a6a 100644 --- a/djangoapps/courseware/modules/seq_module.py +++ b/djangoapps/courseware/modules/seq_module.py @@ -4,12 +4,15 @@ from lxml import etree from mitxmako.shortcuts import render_to_string -from x_module import XModule +from x_module import XModule, XModuleDescriptor # HACK: This shouldn't be hard-coded to two types # OBSOLETE: This obsoletes 'type' class_priority = ['video', 'problem'] +class ModuleDescriptor(XModuleDescriptor): + pass + class Module(XModule): ''' Layout module which lays out content in a temporal sequence ''' @@ -37,24 +40,13 @@ class Module(XModule): def render(self): if self.rendered: return - def j(m): - ''' jsonify contents so it can be embedded in a js array - We also need to split tags so they don't break - mid-string''' - content=json.dumps(m['content']) - content=content.replace('', '<"+"/script>') - - return {'content':content, - 'type': m['type']} - - ## Returns a set of all types of all sub-children child_classes = [set([i.tag for i in e.iter()]) for e in self.xmltree] titles = ["\n".join([i.get("name").strip() for i in e.iter() if i.get("name") is not None]) \ for e in self.xmltree] - self.contents = [j(self.render_function(e)) for e in self.xmltree] + self.contents = self.rendered_children() for contents, title in zip(self.contents, titles): contents['title'] = title @@ -66,19 +58,15 @@ class Module(XModule): new_class = c content['type'] = new_class - params={'items':self.contents, + # Split tags -- browsers handle this as end + # of script, even if it occurs mid-string. Do this after json.dumps()ing + # so that we can be sure of the quotations being used + params={'items':json.dumps(self.contents).replace('', '<"+"/script>'), 'id':self.item_id, 'position': self.position, 'titles':titles, 'tag':self.xmltree.tag} - # TODO/BUG: Destroy JavaScript should only be called for the active view - # This calls it for all the views - # - # To fix this, we'd probably want to have some way of assigning unique - # IDs to sequences. - destroy_js="".join([e['destroy_js'] for e in self.contents if 'destroy_js' in e]) - if self.xmltree.tag in ['sequential', 'videosequence']: self.content=render_to_string('seq_module.html',params) if self.xmltree.tag == 'tab': diff --git a/djangoapps/courseware/modules/template_module.py b/djangoapps/courseware/modules/template_module.py index a8899f985c..938b5c4497 100644 --- a/djangoapps/courseware/modules/template_module.py +++ b/djangoapps/courseware/modules/template_module.py @@ -3,9 +3,12 @@ import os from mitxmako.shortcuts import render_to_response, render_to_string -from x_module import XModule +from x_module import XModule, XModuleDescriptor from lxml import etree +class ModuleDescriptor(XModuleDescriptor): + pass + class Module(XModule): def get_state(self): return json.dumps({ }) diff --git a/djangoapps/courseware/modules/vertical_module.py b/djangoapps/courseware/modules/vertical_module.py index a834614e26..0819fb9f1b 100644 --- a/djangoapps/courseware/modules/vertical_module.py +++ b/djangoapps/courseware/modules/vertical_module.py @@ -2,9 +2,12 @@ import json from mitxmako.shortcuts import render_to_response, render_to_string -from x_module import XModule +from x_module import XModule, XModuleDescriptor from lxml import etree +class ModuleDescriptor(XModuleDescriptor): + pass + class Module(XModule): id_attribute = 'id' diff --git a/djangoapps/courseware/modules/video_module.py b/djangoapps/courseware/modules/video_module.py index 41c76fc717..ad26ede81e 100644 --- a/djangoapps/courseware/modules/video_module.py +++ b/djangoapps/courseware/modules/video_module.py @@ -5,10 +5,13 @@ from lxml import etree from mitxmako.shortcuts import render_to_response, render_to_string -from x_module import XModule +from x_module import XModule, XModuleDescriptor log = logging.getLogger("mitx.courseware.modules") +class ModuleDescriptor(XModuleDescriptor): + pass + class Module(XModule): id_attribute = 'youtube' video_time = 0 diff --git a/djangoapps/courseware/modules/x_module.py b/djangoapps/courseware/modules/x_module.py index 464e2f0a90..93489fab04 100644 --- a/djangoapps/courseware/modules/x_module.py +++ b/djangoapps/courseware/modules/x_module.py @@ -1,3 +1,5 @@ +from lxml import etree + import courseware.progress def dummy_track(event_type, event): @@ -24,6 +26,21 @@ class XModule(object): or a CAPA input type ''' return ['xmodule'] + def get_name(): + name = self.__xmltree.get(name) + if name: + return name + else: + raise "We should iterate through children and find a default name" + + def rendered_children(self): + ''' + Render all children. + This really ought to return a list of xmodules, instead of dictionaries + ''' + children = [self.render_function(e) for e in self.__xmltree] + return children + def __init__(self, system = None, xml = None, item_id = None, json = None, track_url=None, state=None): ''' In most cases, you must pass state or xml''' @@ -38,6 +55,8 @@ class XModule(object): self.json = json self.item_id = item_id self.state = state + + self.__xmltree = etree.fromstring(xml) # PRIVATE if system: ## These are temporary; we really should go @@ -83,14 +102,24 @@ class XModule(object): get is a dictionary-like object ''' return "" - ### Functions used in the CMS + +class XModuleDescriptor(object): + def __init__(self, xml = None, json = None): + if not xml and not json: + raise "XModuleDescriptor must be initalized with XML or JSON" + if not xml: + raise NotImplementedError("Code does not have support for JSON yet") + + self.xml = xml + self.json = json + def get_xml(self): ''' For conversions between JSON and legacy XML representations. ''' if self.xml: return self.xml else: - raise NotImplementedError + raise NotImplementedError("JSON->XML Translation not implemented") def get_json(self): ''' For conversions between JSON and legacy XML representations. @@ -99,14 +128,14 @@ class XModule(object): raise NotImplementedError return self.json # TODO: Return context as well -- files, etc. else: - raise NotImplementedError + raise NotImplementedError("XML->JSON Translation not implemented") - def handle_cms_json(self): - raise NotImplementedError + #def handle_cms_json(self): + # raise NotImplementedError - def render(self, size): - ''' Size: [thumbnail, small, full] - Small ==> what we drag around - Full ==> what we edit - ''' - raise NotImplementedError + #def render(self, size): + # ''' Size: [thumbnail, small, full] + # Small ==> what we drag around + # Full ==> what we edit + # ''' + # raise NotImplementedError diff --git a/djangoapps/courseware/views.py b/djangoapps/courseware/views.py index 0a4fc53fff..8b34e8b26d 100644 --- a/djangoapps/courseware/views.py +++ b/djangoapps/courseware/views.py @@ -1,6 +1,5 @@ import logging import urllib -import json from fs.osfs import OSFS @@ -8,7 +7,7 @@ from django.conf import settings from django.core.context_processors import csrf from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required -from django.http import Http404, HttpResponse +from django.http import Http404 from django.shortcuts import redirect from mitxmako.shortcuts import render_to_response, render_to_string #from django.views.decorators.csrf import ensure_csrf_cookie @@ -16,10 +15,9 @@ from django.views.decorators.cache import cache_control from lxml import etree -from module_render import render_module, make_track_function, I4xSystem +from module_render import render_x_module, make_track_function, I4xSystem from models import StudentModule from student.models import UserProfile -from util.views import accepts from multicourse import multicourse_settings import courseware.content_parser as content_parser @@ -129,7 +127,7 @@ def render_section(request, section): module_object_preload = [] try: - module = render_module(user, request, dom, module_object_preload) + module = render_x_module(user, request, dom, module_object_preload) except: log.exception("Unable to load module") context.update({ @@ -219,7 +217,7 @@ def index(request, course=None, chapter="Using the System", section="Hints"): } try: - module = render_module(user, request, module, module_object_preload) + module = render_x_module(user, request, module, module_object_preload) except: log.exception("Unable to load module") context.update({ @@ -237,78 +235,6 @@ def index(request, course=None, chapter="Using the System", section="Hints"): return result -def modx_dispatch(request, module=None, dispatch=None, id=None): - ''' Generic view for extensions. ''' - if not request.user.is_authenticated(): - return redirect('/') - - # Grab the student information for the module from the database - s = StudentModule.objects.filter(student=request.user, - module_id=id) - #s = StudentModule.get_with_caching(request.user, id) - if len(s) == 0 or s is None: - log.debug("Couldnt find module for user and id " + str(module) + " " + str(request.user) + " "+ str(id)) - raise Http404 - s = s[0] - - oldgrade = s.grade - oldstate = s.state - - dispatch=dispatch.split('?')[0] - - ajax_url = settings.MITX_ROOT_URL + '/modx/'+module+'/'+id+'/' - - # get coursename if stored - coursename = multicourse_settings.get_coursename_from_request(request) - - if coursename and settings.ENABLE_MULTICOURSE: - xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course - data_root = settings.DATA_DIR + xp - else: - data_root = settings.DATA_DIR - - # 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") - if accepts(request, 'text/html'): - return render_to_response("module-error.html", {}) - else: - response = HttpResponse(json.dumps({'success': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) - return response - - # Create the module - system = I4xSystem(track_function = make_track_function(request), - render_function = None, - ajax_url = ajax_url, - filestore = OSFS(data_root), - ) - - try: - instance=courseware.modules.get_module_class(module)(system, - xml, - id, - state=oldstate) - 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': "We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible"})) - return response - - # Let the module handle the AJAX - ajax_return=instance.handle_ajax(dispatch, request.POST) - # Save the state back to the database - s.state=instance.get_state() - if instance.get_score(): - s.grade=instance.get_score()['score'] - if s.grade != oldgrade or s.state != oldstate: - s.save() - # Return whatever the module wanted to return to the client/caller - return HttpResponse(ajax_return) - def quickedit(request, id=None): ''' quick-edit capa problem. diff --git a/run.sh b/run.sh index d63ed291f0..3e273657dc 100755 --- a/run.sh +++ b/run.sh @@ -1 +1 @@ -django-admin.py runserver --settings=envs.dev --pythonpath=. +django-admin.py runserver --settings=envs.dev --pythonpath=. || django-admin runserver --settings=envs.dev --pythonpath=. \ No newline at end of file diff --git a/static/coffee/src/modules/sequence.coffee b/static/coffee/src/modules/sequence.coffee index 0789c7c7f0..56a9b551bf 100644 --- a/static/coffee/src/modules/sequence.coffee +++ b/static/coffee/src/modules/sequence.coffee @@ -39,7 +39,7 @@ class @Sequence $.postWithPrefix "/modx/#{@tag}/#{@id}/goto_position", position: new_position @mark_active new_position - @$('#seq_content').html eval(@elements[new_position - 1].content) + @$('#seq_content').html @elements[new_position - 1].content MathJax.Hub.Queue(["Typeset", MathJax.Hub]) @position = new_position