diff --git a/courseware/capa/capa_problem.py b/courseware/capa/capa_problem.py
index bedd58a33b..75a14ec82c 100644
--- a/courseware/capa/capa_problem.py
+++ b/courseware/capa/capa_problem.py
@@ -11,11 +11,12 @@ import calc, eia
from util import contextualize_text
from inputtypes import textline, schematic
-from responsetypes import numericalresponse, formularesponse, customresponse
+from responsetypes import numericalresponse, formularesponse, customresponse, schematicresponse
response_types = {'numericalresponse':numericalresponse,
'formularesponse':formularesponse,
- 'customresponse':customresponse}
+ 'customresponse':customresponse,
+ 'schematicresponse':schematicresponse}
entry_types = ['textline', 'schematic']
response_properties = ["responseparam", "answer"]
# How to convert from original XML to HTML
@@ -23,6 +24,7 @@ response_properties = ["responseparam", "answer"]
html_transforms = {'problem': {'tag':'div'},
"numericalresponse": {'tag':'span'},
"customresponse": {'tag':'span'},
+ "schematicresponse": {'tag':'span'},
"formularesponse": {'tag':'span'},
"text": {'tag':'span'}}
@@ -36,7 +38,7 @@ global_context={'random':random,
# These should be removed from HTML output, including all subelements
html_problem_semantics = ["responseparam", "answer", "script"]
# These should be removed from HTML output, but keeping subelements
-html_skip = ["numericalresponse", "customresponse", "formularesponse", "text"]
+html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text"]
# These should be transformed
html_special_response = {"textline":textline.render,
"schematic":schematic.render}
diff --git a/courseware/capa/content_parser.py b/courseware/capa/content_parser.py
deleted file mode 100644
index 5b0e197bc5..0000000000
--- a/courseware/capa/content_parser.py
+++ /dev/null
@@ -1,117 +0,0 @@
-try:
- from django.conf import settings
- from auth.models import UserProfile
-except:
- settings = None
-
-from xml.dom.minidom import parse, parseString
-
-from lxml import etree
-
-''' This file will eventually form an abstraction layer between the
-course XML file and the rest of the system.
-
-TODO: Shift everything from xml.dom.minidom to XPath (or XQuery)
-'''
-
-def xpath(xml, query_string, **args):
- ''' Safe xpath query into an xml tree:
- * xml is the tree.
- * query_string is the query
- * args are the parameters. Substitute for {params}.
- We should remove this with the move to lxml.
- We should also use lxml argument passing. '''
- doc = etree.fromstring(xml)
- print type(doc)
- def escape(x):
- # TODO: This should escape the string. For now, we just assume it's made of valid characters.
- # Couldn't figure out how to escape for lxml in a few quick Googles
- valid_chars="".join(map(chr, range(ord('a'),ord('z')+1)+range(ord('A'),ord('Z')+1)+range(ord('0'), ord('9')+1)))+"_ "
- for e in x:
- if e not in valid_chars:
- raise Exception("Invalid char in xpath expression. TODO: Escape")
- return x
-
- args=dict( ((k, escape(args[k])) for k in args) )
- print args
- results = doc.xpath(query_string.format(**args))
- return results
-
-def xpath_remove(tree, path):
- ''' Remove all items matching path from lxml tree. Works in
- place.'''
- items = tree.xpath(path)
- for item in items:
- item.getparent().remove(item)
- return tree
-
-if __name__=='__main__':
- print xpath('', '/{search}/problem[@name="{name}"]', search='html', name="Bob")
-
-def item(l, default="", process=lambda x:x):
- if len(l)==0:
- return default
- elif len(l)==1:
- return process(l[0])
- else:
- raise Exception('Malformed XML')
-
-
-def course_file(user):
- # TODO: Cache. Also, return the libxml2 object.
- return settings.DATA_DIR+UserProfile.objects.get(user=user).courseware
-
-def module_xml(coursefile, module, id_tag, module_id):
- ''' Get XML for a module based on module and module_id. Assumes
- module occurs once in courseware XML file.. '''
- doc = etree.parse(coursefile)
-
- # Sanitize input
- if not module.isalnum():
- raise Exception("Module is not alphanumeric")
- if not module_id.isalnum():
- raise Exception("Module ID is not alphanumeric")
- xpath_search='//*/{module}[(@{id_tag} = "{id}") or (@id = "{id}")]'.format(module=module,
- id_tag=id_tag,
- id=module_id)
- #result_set=doc.xpathEval(xpath_search)
- result_set=doc.xpath(xpath_search)
- if len(result_set)>1:
- print "WARNING: Potentially malformed course file", module, module_id
- if len(result_set)==0:
- return None
- return etree.tostring(result_set[0])
- #return result_set[0].serialize()
-
-def toc_from_xml(coursefile, active_chapter, active_section):
- dom=parse(coursefile)
-
- course = dom.getElementsByTagName('course')[0]
- name=course.getAttribute("name")
- chapters = course.getElementsByTagName('chapter')
- ch=list()
- for c in chapters:
- if c.getAttribute("name") == 'hidden':
- continue
- sections=list()
- for s in c.getElementsByTagName('section'):
- sections.append({'name':s.getAttribute("name"),
- 'time':s.getAttribute("time"),
- 'format':s.getAttribute("format"),
- 'due':s.getAttribute("due"),
- 'active':(c.getAttribute("name")==active_chapter and \
- s.getAttribute("name")==active_section)})
- ch.append({'name':c.getAttribute("name"),
- 'sections':sections,
- 'active':(c.getAttribute("name")==active_chapter)})
- return ch
-
-def dom_select(dom, element_type, element_name):
- if dom==None:
- return None
- elements=dom.getElementsByTagName(element_type)
- for e in elements:
- if e.getAttribute("name")==element_name:
- return e
- return None
-
diff --git a/courseware/capa/inputtypes.py b/courseware/capa/inputtypes.py
index be0bdbcfbd..db98363872 100644
--- a/courseware/capa/inputtypes.py
+++ b/courseware/capa/inputtypes.py
@@ -17,7 +17,19 @@ class schematic(object):
eid = element.get('id')
height = element.get('height')
width = element.get('width')
- context = {'id':eid, 'value':value, 'state':state, 'width':width, 'height':height}
+ parts = element.get('parts')
+ analyses = element.get('analyses')
+ initial_value = element.get('initial_value')
+ context = {
+ 'id':eid,
+ 'value':value,
+ 'initial_value':initial_value,
+ 'state':state,
+ 'width':width,
+ 'height':height,
+ 'parts':parts,
+ 'analyses':analyses,
+ }
html=render_to_string("schematicinput.html", context)
return etree.XML(html)
diff --git a/courseware/capa/responsetypes.py b/courseware/capa/responsetypes.py
index 61ed2b30cb..3e3cefd8ab 100644
--- a/courseware/capa/responsetypes.py
+++ b/courseware/capa/responsetypes.py
@@ -1,4 +1,4 @@
-import random, numpy, math, scipy
+import random, numpy, math, scipy, json
from util import contextualize_text
from calc import evaluator
import random, math
@@ -63,7 +63,6 @@ class customresponse(object):
# be handled by capa_problem
return {}
-
class formularesponse(object):
def __init__(self, xml, context):
self.xml = xml
@@ -114,3 +113,28 @@ class formularesponse(object):
def get_answers(self):
return {self.answer_id:self.correct_answer}
+
+class schematicresponse(object):
+ def __init__(self, xml, context):
+ self.xml = xml
+ self.answer_ids = xml.xpath('//*[@id=$id]//schematic/@id',
+ id=xml.get('id'))
+ self.context = context
+ answer = xml.xpath('//*[@id=$id]//answer',
+ id=xml.get('id'))[0]
+ answer_src = answer.get('src')
+ if answer_src != None:
+ self.code = open(settings.DATA_DIR+'src/'+answer_src).read()
+ else:
+ self.code = answer.text
+
+ def grade(self, student_answers):
+ submission = [json.loads(student_answers[k]) for k in sorted(self.answer_ids)]
+ self.context.update({'submission':submission})
+ exec self.code in global_context, self.context
+ return zip(sorted(self.answer_ids), self.context['correct'])
+
+ def get_answers(self):
+ # Since this is explicitly specified in the problem, this will
+ # be handled by capa_problem
+ return {}
diff --git a/courseware/content_parser.py b/courseware/content_parser.py
index 5d91eb8d06..c5c0600f9d 100644
--- a/courseware/content_parser.py
+++ b/courseware/content_parser.py
@@ -1,9 +1,13 @@
-from django.conf import settings
-from xml.dom.minidom import parse, parseString
+try:
+ from django.conf import settings
+ from auth.models import UserProfile
+except:
+ settings = None
from lxml import etree
-from auth.models import UserProfile
+import json
+import hashlib
''' This file will eventually form an abstraction layer between the
course XML file and the rest of the system.
@@ -11,11 +15,18 @@ course XML file and the rest of the system.
TODO: Shift everything from xml.dom.minidom to XPath (or XQuery)
'''
+def fasthash(string):
+ m = hashlib.new("md4")
+ m.update(string)
+ return "id"+m.hexdigest()
+
def xpath(xml, query_string, **args):
''' Safe xpath query into an xml tree:
* xml is the tree.
* query_string is the query
- * args are the parameters. Substitute for {params}. '''
+ * args are the parameters. Substitute for {params}.
+ We should remove this with the move to lxml.
+ We should also use lxml argument passing. '''
doc = etree.fromstring(xml)
print type(doc)
def escape(x):
@@ -32,8 +43,17 @@ def xpath(xml, query_string, **args):
results = doc.xpath(query_string.format(**args))
return results
+def xpath_remove(tree, path):
+ ''' Remove all items matching path from lxml tree. Works in
+ place.'''
+ items = tree.xpath(path)
+ for item in items:
+ item.getparent().remove(item)
+ return tree
+
if __name__=='__main__':
- print xpath('', '/{search}/problem[@name="{name}"]', search='html', name="Bob")
+ print xpath('', '/{search}/problem[@name="{name}"]',
+ search='html', name="Bob")
def item(l, default="", process=lambda x:x):
if len(l)==0:
@@ -42,16 +62,39 @@ def item(l, default="", process=lambda x:x):
return process(l[0])
else:
raise Exception('Malformed XML')
+
+def id_tag(course):
+ ''' Tag all course elements with unique IDs '''
+ default_ids = {'video':'youtube',
+ 'problem':'filename',
+ 'sequential':'id',
+ 'html':'filename',
+ 'vertical':'id',
+ 'tab':'id',
+ 'schematic':'id'}
+ # Tag elements with unique IDs
+ elements = course.xpath("|".join(['//'+c for c in default_ids]))
+ for elem in elements:
+ if elem.get('id'):
+ pass
+ elif elem.get(default_ids[elem.tag]):
+ new_id = elem.get(default_ids[elem.tag]) # Convert to alphanumeric
+ new_id = "".join([a for a in new_id if a.isalnum()])
+ elem.set('id', new_id)
+ else:
+ elem.set('id', fasthash(etree.tostring(elem)))
def course_file(user):
- # TODO: Cache. Also, return the libxml2 object.
- return settings.DATA_DIR+UserProfile.objects.get(user=user).courseware
+ # TODO: Cache.
+ tree = etree.parse(settings.DATA_DIR+UserProfile.objects.get(user=user).courseware)
+ id_tag(tree)
+ return tree
def module_xml(coursefile, module, id_tag, module_id):
''' Get XML for a module based on module and module_id. Assumes
module occurs once in courseware XML file.. '''
- doc = etree.parse(coursefile)
+ doc = coursefile
# Sanitize input
if not module.isalnum():
@@ -70,35 +113,24 @@ def module_xml(coursefile, module, id_tag, module_id):
return etree.tostring(result_set[0])
#return result_set[0].serialize()
-def toc_from_xml(coursefile, active_chapter, active_section):
- dom=parse(coursefile)
+def toc_from_xml(dom, active_chapter, active_section):
+ name = dom.xpath('//course/@name')[0]
- course = dom.getElementsByTagName('course')[0]
- name=course.getAttribute("name")
- chapters = course.getElementsByTagName('chapter')
+ chapters = dom.xpath('//course[@name=$name]/chapter', name=name)
ch=list()
for c in chapters:
- if c.getAttribute("name") == 'hidden':
+ if c.get('name') == 'hidden':
continue
sections=list()
- for s in c.getElementsByTagName('section'):
- sections.append({'name':s.getAttribute("name"),
- 'time':s.getAttribute("time"),
- 'format':s.getAttribute("format"),
- 'due':s.getAttribute("due"),
- 'active':(c.getAttribute("name")==active_chapter and \
- s.getAttribute("name")==active_section)})
- ch.append({'name':c.getAttribute("name"),
+ for s in dom.xpath('//course[@name=$name]/chapter[@name=$chname]/section', name=name, chname=c.get('name')):
+ sections.append({'name':s.get("name") or "",
+ 'time':s.get("time") or "",
+ 'format':s.get("format") or "",
+ 'due':s.get("due") or "",
+ 'active':(c.get("name")==active_chapter and \
+ s.get("name")==active_section)})
+ ch.append({'name':c.get("name"),
'sections':sections,
- 'active':(c.getAttribute("name")==active_chapter)})
+ 'active':(c.get("name")==active_chapter)})
return ch
-def dom_select(dom, element_type, element_name):
- if dom==None:
- return None
- elements=dom.getElementsByTagName(element_type)
- for e in elements:
- if e.getAttribute("name")==element_name:
- return e
- return None
-
diff --git a/courseware/module_render.py b/courseware/module_render.py
index 58574df190..9375facc4c 100644
--- a/courseware/module_render.py
+++ b/courseware/module_render.py
@@ -16,10 +16,12 @@ from django.http import Http404
import urllib
-import capa_module
-import video_module
-import html_module
-import schematic_module
+import courseware.modules.capa_module
+import courseware.modules.video_module
+import courseware.modules.vertical_module
+import courseware.modules.html_module
+import courseware.modules.schematic_module
+import courseware.modules.seq_module
from models import StudentModule
@@ -29,12 +31,18 @@ from django.conf import settings
import content_parser
+import sys
+
+from lxml import etree
import uuid
-modx_modules={'problem':capa_module.LoncapaModule,
- 'video':video_module.VideoModule,
- 'html':html_module.HtmlModule,
- 'schematic':schematic_module.SchematicModule}
+## TODO: Add registration mechanism
+modx_modules={'problem':courseware.modules.capa_module.LoncapaModule,
+ 'video':courseware.modules.video_module.VideoModule,
+ 'html':courseware.modules.html_module.HtmlModule,
+ 'vertical':courseware.modules.vertical_module.VerticalModule,
+ 'sequential':courseware.modules.seq_module.SequentialModule,
+ 'schematic':courseware.modules.schematic_module.SchematicModule}
def make_track_function(request):
def f(event_type, event):
@@ -44,11 +52,12 @@ def make_track_function(request):
def modx_dispatch(request, module=None, dispatch=None, id=None):
''' Generic view for extensions. '''
# Grab the student information for the module from the database
+ print module, request.user, id
s = StudentModule.objects.filter(module_type=module,
student=request.user,
module_id=id)
if len(s) == 0:
- print "ls404"
+ print "ls404", module, request.user, id
raise Http404
s=s[0]
@@ -67,83 +76,25 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
s.module_id,
ajax_url=ajax_url,
state=s.state,
- track_function = make_track_function(request))
+ track_function = make_track_function(request),
+ render_function = render_module,
+ meta = request)
# 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()
- s.grade=instance.get_score()['score']
+ if instance.get_score() != None:
+ s.grade=instance.get_score()['score']
s.save()
# Return whatever the module wanted to return to the client/caller
return HttpResponse(ajax_return)
-def vertical_module(request, module):
- ''' Layout module which lays out content vertically.
- '''
- contents=[(e.getAttribute("name"),render_module(request, e)) \
- for e in module.childNodes \
- if e.nodeType==1]
- init_js="".join([e[1]['init_js'] for e in contents if 'init_js' in e[1]])
- destroy_js="".join([e[1]['destroy_js'] for e in contents if 'destroy_js' in e[1]])
-
- return {'init_js':init_js,
- 'destroy_js':destroy_js,
- 'content':render_to_string('vert_module.html',{'items':contents}),
- 'type':'vertical'}
-
-def seq_module(request, module):
- ''' Layout module which lays out content in a temporal sequence
- '''
- 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
- if 'init_js' not in m: m['init_js']=""
- if 'type' not in m: m['init_js']=""
- content=json.dumps(m['content'])
- content=content.replace('', '<"+"/script>')
-
- return {'content':content,
- "destroy_js":m['destroy_js'],
- 'init_js':m['init_js'],
- 'type':m['type']}
- contents=[(e.getAttribute("name"),j(render_module(request, e))) \
- for e in module.childNodes \
- if e.nodeType==1]
-
- js=""
-
- iid=uuid.uuid1().hex
-
- params={'items':contents,
- 'id':"seq"}
-
- # 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[1]['destroy_js'] for e in contents if 'destroy_js' in e[1]])
-
- if module.nodeName == 'sequential':
- return {'init_js':js+render_to_string('seq_module.js',params),
- "destroy_js":destroy_js,
- 'content':render_to_string('seq_module.html',params),
- 'type':'sequential'}
- if module.nodeName == 'tab':
- params['id'] = 'tab'
- return {'init_js':js+render_to_string('tab_module.js',params),
- "destroy_js":destroy_js,
- 'content':render_to_string('tab_module.html',params),
- 'type':'tab'}
-
-
def render_x_module(request, xml_module):
''' Generic module for extensions. This renders to HTML. '''
# Check if problem has an instance in DB
- module_type=xml_module.nodeName
+ module_type=xml_module.tag
module_class=modx_modules[module_type]
- module_id=xml_module.getAttribute(module_class.id_attribute)
+ module_id=xml_module.get('id') #module_class.id_attribute) or ""
# Grab state from database
s = StudentModule.objects.filter(student=request.user,
@@ -157,11 +108,13 @@ def render_x_module(request, xml_module):
# Create a new instance
ajax_url = '/modx/'+module_type+'/'+module_id+'/'
- instance=module_class(xml_module.toxml(),
+ instance=module_class(etree.tostring(xml_module),
module_id,
ajax_url=ajax_url,
state=state,
- track_function = make_track_function(request))
+ track_function = make_track_function(request),
+ render_function = render_module,
+ meta = request)
# If instance wasn't already in the database, create it
if len(s) == 0:
@@ -179,20 +132,8 @@ def render_x_module(request, xml_module):
return content
-module_types={'video':render_x_module,
- 'html':render_x_module,
- 'tab':seq_module,
- 'vertical':vertical_module,
- 'sequential':seq_module,
- 'problem':render_x_module,
- 'schematic':render_x_module
- }
-
def render_module(request, module):
''' Generic dispatch for internal modules. '''
- if module==None:
+ if module==None :
return {"content":""}
- if str(module.localName) in module_types:
- return module_types[module.localName](request, module)
- print "rm404"
- raise Http404
+ return render_x_module(request, module)
diff --git a/courseware/modules/__init__.py b/courseware/modules/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/courseware/capa_module.py b/courseware/modules/capa_module.py
similarity index 98%
rename from courseware/capa_module.py
rename to courseware/modules/capa_module.py
index e13ee453fe..591f9c7516 100644
--- a/courseware/capa_module.py
+++ b/courseware/modules/capa_module.py
@@ -2,14 +2,14 @@ import random, numpy, math, scipy, sys, StringIO, os, struct, json
from x_module import XModule
import sys
-from capa.capa_problem import LoncapaProblem
+from courseware.capa.capa_problem import LoncapaProblem
from django.http import Http404
import dateutil
import dateutil.parser
import datetime
-import content_parser
+import courseware.content_parser as content_parser
from lxml import etree
@@ -108,8 +108,8 @@ class LoncapaModule(XModule):
return html
- def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None):
- XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function)
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
self.attempts = 0
self.max_attempts = None
diff --git a/courseware/html_module.py b/courseware/modules/html_module.py
similarity index 90%
rename from courseware/html_module.py
rename to courseware/modules/html_module.py
index 853a322236..ce26ea0045 100644
--- a/courseware/html_module.py
+++ b/courseware/modules/html_module.py
@@ -25,8 +25,8 @@ class HtmlModule(XModule):
textlist=[i for i in textlist if type(i)==str]
return "".join(textlist)
- def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None):
- XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function)
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
xmltree=etree.fromstring(xml)
self.filename = None
filename_l=xmltree.xpath("/html/@filename")
diff --git a/courseware/schematic_module.py b/courseware/modules/schematic_module.py
similarity index 88%
rename from courseware/schematic_module.py
rename to courseware/modules/schematic_module.py
index 5a7fa390a0..b97e80541b 100644
--- a/courseware/schematic_module.py
+++ b/courseware/modules/schematic_module.py
@@ -18,6 +18,6 @@ class SchematicModule(XModule):
def get_html(self):
return ''.format(item_id=self.item_id)
- def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None):
- XModule.__init__(self, xml, item_id, ajax_url, track_url, state)
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, render_function = None, meta = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, render_function)
diff --git a/courseware/modules/seq_module.py b/courseware/modules/seq_module.py
new file mode 100644
index 0000000000..5845c23a55
--- /dev/null
+++ b/courseware/modules/seq_module.py
@@ -0,0 +1,87 @@
+from x_module import XModule
+from lxml import etree
+from django.http import Http404
+
+import json
+
+## TODO: Abstract out from Django
+from django.conf import settings
+from djangomako.shortcuts import render_to_response, render_to_string
+
+class SequentialModule(XModule):
+ ''' Layout module which lays out content in a temporal sequence
+ '''
+ id_attribute = 'id'
+
+ def get_state(self):
+ return json.dumps({ 'position':self.position })
+
+ def get_xml_tags():
+ return ["sequential", 'tab']
+
+ def get_html(self):
+ return self.content
+
+ def get_init_js(self):
+ return self.init_js
+
+ def get_destroy_js(self):
+ return self.destroy_js
+
+ def handle_ajax(self, dispatch, get):
+ print "GET", get
+ print "DISPATCH", dispatch
+ if dispatch=='goto_position':
+ self.position = int(get['position'])
+ return json.dumps({'success':True})
+ raise Http404()
+
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
+ xmltree=etree.fromstring(xml)
+
+ self.position = 1
+
+ if state!=None:
+ state = json.loads(state)
+ if 'position' in state: self.position = int(state['position'])
+
+ 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'''
+ if 'init_js' not in m: m['init_js']=""
+ if 'type' not in m: m['init_js']=""
+ content=json.dumps(m['content'])
+ content=content.replace('', '<"+"/script>')
+
+ return {'content':content,
+ "destroy_js":m['destroy_js'],
+ 'init_js':m['init_js'],
+ 'type':m['type']}
+
+ contents=[(e.get("name"),j(render_function(meta, e))) \
+ for e in xmltree]
+
+ js=""
+
+ params={'items':contents,
+ 'id':item_id,
+ 'position': self.position}
+
+ # 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[1]['destroy_js'] for e in contents if 'destroy_js' in e[1]])
+
+ if xmltree.tag == 'sequential':
+ self.init_js=js+render_to_string('seq_module.js',params)
+ self.destroy_js=destroy_js
+ self.content=render_to_string('seq_module.html',params)
+ if xmltree.tag == 'tab':
+ params['id'] = 'tab'
+ self.init_js=js+render_to_string('tab_module.js',params)
+ self.destroy_js=destroy_js
+ self.content=render_to_string('tab_module.html',params)
diff --git a/courseware/modules/vertical_module.py b/courseware/modules/vertical_module.py
new file mode 100644
index 0000000000..b5ad1be368
--- /dev/null
+++ b/courseware/modules/vertical_module.py
@@ -0,0 +1,34 @@
+from x_module import XModule
+from lxml import etree
+
+import json
+
+## TODO: Abstract out from Django
+from django.conf import settings
+from djangomako.shortcuts import render_to_response, render_to_string
+
+class VerticalModule(XModule):
+ id_attribute = 'id'
+
+ def get_state(self):
+ return json.dumps({ })
+
+ def get_xml_tags():
+ return "vertical"
+
+ def get_html(self):
+ return render_to_string('vert_module.html',{'items':self.contents})
+
+ def get_init_js(self):
+ return self.init_js_text
+
+ def get_destroy_js(self):
+ return self.destroy_js_text
+
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
+ xmltree=etree.fromstring(xml)
+ self.contents=[(e.get("name"),self.render_function(meta, e)) \
+ for e in xmltree]
+ self.init_js_text="".join([e[1]['init_js'] for e in self.contents if 'init_js' in e[1]])
+ self.destroy_js_text="".join([e[1]['destroy_js'] for e in self.contents if 'destroy_js' in e[1]])
diff --git a/courseware/video_module.py b/courseware/modules/video_module.py
similarity index 53%
rename from courseware/video_module.py
rename to courseware/modules/video_module.py
index 0fdb54f119..6fadc17941 100644
--- a/courseware/video_module.py
+++ b/courseware/modules/video_module.py
@@ -1,4 +1,5 @@
from x_module import XModule
+from lxml import etree
import json
@@ -7,47 +8,55 @@ from django.conf import settings
from djangomako.shortcuts import render_to_response, render_to_string
class VideoModule(XModule):
- id_attribute = 'youtube'
+ #id_attribute = 'youtube'
video_time = 0
def handle_ajax(self, dispatch, get):
- if dispatch == 'time':
- self.video_time = int(get['time'])
- print self.video_time
-
- return json.dumps("True")
+ print "GET", get
+ print "DISPATCH", dispatch
+ if dispatch=='goto_position':
+ self.position = int(float(get['position']))
+ print "NEW POSITION", self.position
+ return json.dumps({'success':True})
+ raise Http404()
def get_state(self):
- return json.dumps({ 'time':self.video_time })
+ print "STATE POSITION", self.position
+ return json.dumps({ 'position':self.position })
def get_xml_tags():
''' Tags in the courseware file guaranteed to correspond to the module '''
return "video"
def video_list(self):
- l=self.item_id.split(',')
+ l=self.youtube.split(',')
l=[i.split(":") for i in l]
return json.dumps(dict(l))
def get_html(self):
return render_to_string('video.html',{'streams':self.video_list(),
'id':self.item_id,
- 'video_time':self.video_time})
+ 'position':self.position})
def get_init_js(self):
''' JavaScript code to be run when problem is shown. Be aware
that this may happen several times on the same page
(e.g. student switching tabs). Common functions should be put
in the main course .js files for now. '''
+ print "INIT POSITION", self.position
return render_to_string('video_init.js',{'streams':self.video_list(),
'id':self.item_id,
- 'video_time':self.video_time})
+ 'position':self.position})
def get_destroy_js(self):
- return "videoDestroy();"
+ return "videoDestroy(\""+self.item_id+"\");"
- def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None):
- XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function)
- print state
- if state!=None and "time" not in json.loads(state):
- self.video_time = 0
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
+ self.youtube = etree.XML(xml).get('youtube')
+ self.position = 0
+ if state!=None:
+ state = json.loads(state)
+ if 'position' in state: self.position = int(float(state['position']))
+ print "POOSITION IN STATE"
+ print "LOAD POSITION", self.position
diff --git a/courseware/x_module.py b/courseware/modules/x_module.py
similarity index 78%
rename from courseware/x_module.py
rename to courseware/modules/x_module.py
index 2b79519d20..75ff038f23 100644
--- a/courseware/x_module.py
+++ b/courseware/modules/x_module.py
@@ -39,11 +39,13 @@ class XModule(object):
get is a dictionary-like object '''
return ""
- def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None):
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None, meta = None):
''' In most cases, you must pass state or xml'''
- self.xml=xml
- self.item_id=item_id
- self.ajax_url=ajax_url
- self.track_url=track_url
- self.state=state
- self.tracker=track_function
+ self.xml = xml
+ self.item_id = item_id
+ self.ajax_url = ajax_url
+ self.track_url = track_url
+ self.state = state
+ self.tracker = track_function
+ self.render_function = render_function
+ self.meta = meta
diff --git a/courseware/views.py b/courseware/views.py
index 03a7a05965..be93b8131e 100644
--- a/courseware/views.py
+++ b/courseware/views.py
@@ -1,7 +1,6 @@
from django.http import HttpResponse
from django.template import Context, loader
from djangomako.shortcuts import render_to_response, render_to_string
-from xml.dom.minidom import parse, parseString
import json, os, sys
from django.core.context_processors import csrf
@@ -14,11 +13,6 @@ import StringIO
from django.http import Http404
-import urllib
-
-import capa_module
-import video_module
-
from models import StudentModule
import urllib
@@ -31,6 +25,11 @@ import uuid
from module_render import *
+from lxml import etree
+
+etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
+ remove_comments = True))
+
template_imports={'urllib':urllib}
def profile(request):
@@ -39,20 +38,23 @@ def profile(request):
if not request.user.is_authenticated():
return redirect('/')
- dom=parse(content_parser.course_file(request.user))
+ dom=content_parser.course_file(request.user)
hw=[]
- course = dom.getElementsByTagName('course')[0]
- chapters = course.getElementsByTagName('chapter')
+ course = dom.xpath('//course/@name')[0]
+ chapters = dom.xpath('//course[@name=$course]/chapter', course=course)
responses=StudentModule.objects.filter(student=request.user)
for c in chapters:
- for s in c.getElementsByTagName('section'):
- problems=s.getElementsByTagName('problem')
+ chname=c.get('name')
+ for s in dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section',
+ course=course, chname=chname):
+ problems=dom.xpath('//course[@name=$course]/chapter[@name=$chname]/section[@name=$section]//problem',
+ course=course, chname=chname, section=s.get('name'))
scores=[]
if len(problems)>0:
for p in problems:
- id = p.getAttribute('filename')
+ id = p.get('filename')
correct = 0
for response in responses:
if response.module_id == id:
@@ -60,11 +62,11 @@ def profile(request):
correct=response.grade
else:
correct=0
- total=capa_module.LoncapaModule(p.toxml(), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores?
+ total=courseware.modules.capa_module.LoncapaModule(etree.tostring(p), "id").max_score() # TODO: Add state. Not useful now, but maybe someday problems will have randomized max scores?
scores.append((int(correct),total))
- score={'course':course.getAttribute('name'),
- 'section':s.getAttribute("name"),
- 'chapter':c.getAttribute("name"),
+ score={'course':course,
+ 'section':s.get("name"),
+ 'chapter':c.get("name"),
'scores':scores,
}
hw.append(score)
@@ -109,6 +111,7 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
# Fixes URLs -- we don't get funny encoding characters from spaces
# so they remain readable
+ ## TODO: Properly replace underscores
course=course.replace("_"," ")
chapter=chapter.replace("_"," ")
section=section.replace("_"," ")
@@ -118,15 +121,13 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
if course!="6.002 Spring 2012":
return redirect('/')
- cf = content_parser.course_file(request.user)
- dom=parse(cf)
- dom_course=content_parser.dom_select(dom, 'course', course)
- dom_chapter=content_parser.dom_select(dom_course, 'chapter', chapter)
- dom_section=content_parser.dom_select(dom_chapter, 'section', section)
- if dom_section!=None:
- module=[e for e in dom_section.childNodes if e.nodeType==1][0]
+ dom = content_parser.course_file(request.user)
+ dom_module = dom.xpath("//course[@name=$course]/chapter[@name=$chapter]//section[@name=$section]/*[1]",
+ course=course, chapter=chapter, section=section)
+ if len(dom_module) == 0:
+ module = None
else:
- module=None
+ module = dom_module[0]
accordion=render_accordion(request, course, chapter, section)
@@ -135,6 +136,8 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
if 'init_js' not in module:
module['init_js']=''
+
+
context={'init':accordion['init_js']+module['init_js'],
'accordion':accordion['content'],
'content':module['content'],
diff --git a/settings_new_askbot.py b/settings_new_askbot.py
deleted file mode 100644
index 865b0b2c80..0000000000
--- a/settings_new_askbot.py
+++ /dev/null
@@ -1,256 +0,0 @@
-import os
-import sys
-
-import djcelery
-
-COURSEWARE_ENABLED = True
-ASKBOT_ENABLED = True
-
-CSRF_COOKIE_DOMAIN = '127.0.0.1'
-
-# Defaults to be overridden
-EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-SITE_NAME = "localhost:8000"
-
-DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu'
-DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu'
-
-GENERATE_RANDOM_USER_CREDENTIALS = False
-
-WIKI_REQUIRE_LOGIN_EDIT = True
-WIKI_REQUIRE_LOGIN_VIEW = True
-
-PERFSTATS = False
-
-HTTPS = 'on'
-
-MEDIA_URL = ''
-MEDIA_ROOT = ''
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-
-ADMINS = (
- ('Piotr Mitros', 'pmitros@csail.mit.edu'),
-)
-
-MANAGERS = ADMINS
-
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-TIME_ZONE = 'America/Chicago'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en'
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale
-USE_L10N = True
-
-STATIC_URL = '/static/'
-
-# URL prefix for admin static files -- CSS, JavaScript and images.
-# Make sure to use a trailing slash.
-# Examples: "http://foo.com/static/admin/", "/static/admin/".
-ADMIN_MEDIA_PREFIX = '/static/admin/'
-
-# List of finder classes that know how to find static files in
-# various locations.
-STATICFILES_FINDERS = (
- 'django.contrib.staticfiles.finders.FileSystemFinder',
- 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
-# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
-)
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
-# 'django.template.loaders.eggs.Loader',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'track.middleware.TrackMiddleware',
- 'djangomako.middleware.MakoMiddleware',
- #'debug_toolbar.middleware.DebugToolbarMiddleware',
-)
-
-ROOT_URLCONF = 'mitx.urls'
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'courseware',
- 'auth',
- 'django.contrib.humanize',
- 'static_template_view',
- 'staticbook',
- 'simplewiki',
- 'track',
- 'circuit',
- 'perfstats',
- # Uncomment the next line to enable the admin:
- # 'django.contrib.admin',
- # Uncomment the next line to enable admin documentation:
- # 'django.contrib.admindocs',
-)
-
-# A sample logging configuration. The only tangible logging
-# performed by this configuration is to send an email to
-# the site admins on every HTTP 500 error.
-# See http://docs.djangoproject.com/en/dev/topics/logging for
-# more details on how to customize your logging configuration.
-LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'handlers': {
- 'mail_admins': {
- 'level': 'ERROR',
- 'class': 'django.utils.log.AdminEmailHandler'
- }
- },
- 'loggers': {
- 'django.request': {
- 'handlers': ['mail_admins'],
- 'level': 'ERROR',
- 'propagate': True,
- },
- }
-}
-
-#TRACK_DIR = None
-DEBUG_TRACK_LOG = False
-# Maximum length of a tracking string. We don't want e.g. a file upload in our log
-TRACK_MAX_EVENT = 1000
-# Maximum length of log file before starting a new one.
-MAXLOG = 500
-
-# Our parent dir (mitx_all) is the BASE_DIR
-BASE_DIR = os.path.abspath(os.path.join(__file__, "..", ".."))
-
-# Make sure we execute correctly regardless of where we're called from
-execfile(os.path.join(BASE_DIR, "settings.py"))
-
-
-if PERFSTATS :
- MIDDLEWARE_CLASSES = ( 'perfstats.middleware.ProfileMiddleware',) + MIDDLEWARE_CLASSES
-
-if 'TRACK_DIR' not in locals():
- TRACK_DIR = BASE_DIR+'/track_dir/'
-if 'STATIC_ROOT' not in locals():
- STATIC_ROOT = BASE_DIR+'/staticroot/'
-if 'DATA_DIR' not in locals():
- DATA_DIR = BASE_DIR+'/data/'
-if 'TEXTBOOK_DIR' not in locals():
- TEXTBOOK_DIR = BASE_DIR+'/textbook/'
-
-if 'TEMPLATE_DIRS' not in locals():
- TEMPLATE_DIRS = (
- BASE_DIR+'/templates/',
- DATA_DIR+'/templates',
- TEXTBOOK_DIR,
- )
-
-if 'STATICFILES_DIRS' not in locals():
- STATICFILES_DIRS = (
- BASE_DIR+'/3rdParty/static',
- BASE_DIR+'/static',
- )
-
-if 'ASKBOT_EXTRA_SKINS_DIR' not in locals():
- ASKBOT_EXTRA_SKINS_DIR = BASE_DIR+'/askbot-devel/askbot/skins'
-if 'ASKBOT_DIR' not in locals():
- ASKBOT_DIR = BASE_DIR+'/askbot-devel'
-
-sys.path.append(ASKBOT_DIR)
-import askbot
-import site
-
-STATICFILES_DIRS = STATICFILES_DIRS + ( ASKBOT_DIR+'/askbot/skins',)
-
-# Needed for Askbot
-# Critical TODO: Move to S3
-MEDIA_URL = '/discussion/upfiles/'
-MEDIA_ROOT = ASKBOT_DIR+'/askbot/upfiles'
-
-ASKBOT_ROOT = os.path.dirname(askbot.__file__)
-
-site.addsitedir(os.path.join(os.path.dirname(askbot.__file__), 'deps'))
-TEMPLATE_LOADERS = TEMPLATE_LOADERS + ('askbot.skins.loaders.filesystem_load_template_source',)
-
-MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
- 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
- 'askbot.middleware.forum_mode.ForumModeMiddleware',
- 'askbot.middleware.cancel.CancelActionMiddleware',
- 'django.middleware.transaction.TransactionMiddleware',
- #'debug_toolbar.middleware.DebugToolbarMiddleware',
- 'askbot.middleware.view_log.ViewLogMiddleware',
- 'askbot.middleware.spaceless.SpacelessMiddleware',
- # 'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
-)
-
-FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
-FILE_UPLOAD_HANDLERS = (
- 'django.core.files.uploadhandler.MemoryFileUploadHandler',
- 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
-)
-ASKBOT_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
-ASKBOT_MAX_UPLOAD_FILE_SIZE = 1024 * 1024 #result in bytes
-# ASKBOT_FILE_UPLOAD_DIR = os.path.join(os.path.dirname(__file__), 'askbot', 'upfiles')
-DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
-
-PROJECT_ROOT = os.path.dirname(__file__)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.core.context_processors.request',
- 'askbot.context.application_settings',
- #'django.core.context_processors.i18n',
- 'askbot.user_messages.context_processors.user_messages',#must be before auth
- 'django.core.context_processors.auth', #this is required for admin
- 'django.core.context_processors.csrf', #necessary for csrf protection
-)
-
-INSTALLED_APPS = INSTALLED_APPS + (
- 'django.contrib.sitemaps',
- 'django.contrib.admin',
- 'south',
- 'askbot.deps.livesettings',
- 'askbot',
- #'keyedcache', # TODO: Main askbot tree has this installed, but we get intermittent errors if we include it.
- 'robots',
- 'django_countries',
- 'djcelery',
- 'djkombu',
- 'followit',
-)
-
-CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
-ASKBOT_URL = 'discussion/'
-LOGIN_REDIRECT_URL = '/'
-LOGIN_URL = '/'
-
-# ASKBOT_UPLOADED_FILES_URL = '%s%s' % (ASKBOT_URL, 'upfiles/')
-ALLOW_UNICODE_SLUGS = False
-ASKBOT_USE_STACKEXCHANGE_URLS = False #mimic url scheme of stackexchange
-ASKBOT_CSS_DEVEL = True
-
-# Celery Settings
-BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"
-CELERY_ALWAYS_EAGER = True
-
-djcelery.setup_loader()
diff --git a/settings_new_askbot.py b/settings_new_askbot.py
new file mode 120000
index 0000000000..57b3227b1f
--- /dev/null
+++ b/settings_new_askbot.py
@@ -0,0 +1 @@
+settings_old_askbot.py
\ No newline at end of file
diff --git a/simplewiki/views.py b/simplewiki/views.py
index 697a40fd2b..3fbd1f3b9a 100644
--- a/simplewiki/views.py
+++ b/simplewiki/views.py
@@ -48,7 +48,7 @@ def root_redirect(request):
try:
root = Article.get_root()
except:
- err = not_found(request, 'mainpage')
+ err = not_found(request, '/')
return err
return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url())))
@@ -92,7 +92,7 @@ def create(request, wiki_url):
#except ShouldHaveExactlyOneRootSlug, (e):
except:
if Article.objects.filter(parent=None).count() > 0:
- return HttpResponseRedirect(reverse('wiki_view', args=('',)))
+ return HttpResponseRedirect(reverse('wiki_view', args=('/',)))
# Root not found...
path = []
url_path = [""]
@@ -380,7 +380,7 @@ def fetch_from_url(request, url):
try:
root = Article.get_root()
except:
- err = not_found(request, '')
+ err = not_found(request, '/')
return (article, path, err)
if url_path and root.slug == url_path[0]:
diff --git a/static_template_view/views.py b/static_template_view/views.py
index b290f84473..5579e20c86 100644
--- a/static_template_view/views.py
+++ b/static_template_view/views.py
@@ -8,7 +8,12 @@ from django.shortcuts import redirect
from django.core.context_processors import csrf
#valid_templates=['index.html', 'staff.html', 'info.html', 'credits.html']
-valid_templates=['mitx_global.html', 'index.html']
+valid_templates=['mitx_global.html',
+ 'index.html',
+ 'tos.html',
+ 'privacy.html',
+ 'honor.html',
+ 'copyright.html']
def index(request, template):
csrf_token = csrf(request)['csrf_token']