diff --git a/courseware/content_parser.py b/courseware/content_parser.py
index 8f6482ccf0..c296e30f3d 100644
--- a/courseware/content_parser.py
+++ b/courseware/content_parser.py
@@ -49,7 +49,7 @@ def xpath(xml, query_string, **args):
We should remove this with the move to lxml.
We should also use lxml argument passing. '''
doc = etree.fromstring(xml)
- print type(doc)
+ #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
@@ -60,7 +60,7 @@ def xpath(xml, query_string, **args):
return x
args=dict( ((k, escape(args[k])) for k in args) )
- print args
+ #print args
results = doc.xpath(query_string.format(**args))
return results
@@ -86,14 +86,20 @@ def item(l, default="", process=lambda x:x):
def id_tag(course):
''' Tag all course elements with unique IDs '''
- default_ids = {'video':'youtube',
+ old_ids = {'video':'youtube',
'problem':'filename',
'sequential':'id',
'html':'filename',
'vertical':'id',
'tab':'id',
- 'schematic':'id'}
-
+ 'schematic':'id',
+ 'book' : 'id'}
+ import courseware.modules
+ default_ids = courseware.modules.get_default_ids()
+
+ #print default_ids, old_ids
+ #print default_ids == old_ids
+
# Tag elements with unique IDs
elements = course.xpath("|".join(['//'+c for c in default_ids]))
for elem in elements:
@@ -143,7 +149,7 @@ def course_file(user):
filename = UserProfile.objects.get(user=user).courseware
data_template = template_lookup.get_template(filename)
- options = {'dev_content':True}
+ options = {'dev_content':settings.DEV_CONTENT}
tree = etree.XML(data_template.render(**options))
id_tag(tree)
diff --git a/courseware/management/commands/check_course.py b/courseware/management/commands/check_course.py
index 7ca8ef8242..b020529ffb 100644
--- a/courseware/management/commands/check_course.py
+++ b/courseware/management/commands/check_course.py
@@ -41,6 +41,7 @@ class Command(BaseCommand):
if os.path.exists(sections_dir):
print "Checking all section includes are valid XML"
for f in os.listdir(sections_dir):
+ print f
etree.parse(sections_dir+'/'+f)
else:
print "Skipping check of include files -- no section includes dir ("+sections_dir+")"
diff --git a/courseware/module_render.py b/courseware/module_render.py
index 3c863684be..1f4e784b2e 100644
--- a/courseware/module_render.py
+++ b/courseware/module_render.py
@@ -26,24 +26,10 @@ import track.views
import courseware.content_parser as content_parser
-import courseware.modules.capa_module
-import courseware.modules.html_module
-import courseware.modules.schematic_module
-import courseware.modules.seq_module
-import courseware.modules.vertical_module
-import courseware.modules.video_module
+import courseware.modules
log = logging.getLogger("mitx.courseware")
-## 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,
- 'tab':courseware.modules.seq_module.SequentialModule,
- 'schematic':courseware.modules.schematic_module.SchematicModule}
-
def object_cache(cache, user, module_type, module_id):
# We don't look up on user -- all queries include user
# Additional lookup would require a DB hit the way Django
@@ -74,18 +60,18 @@ def modx_dispatch(request, module=None, dispatch=None, id=None):
ajax_url = '/modx/'+module+'/'+id+'/'
- id_tag=modx_modules[module].id_attribute
+ id_tag=courseware.modules.get_module_class(module.id_attribute)
# Grab the XML corresponding to the request from course.xml
xml = content_parser.module_xml(content_parser.course_file(request.user), module, id_tag, id)
# Create the module
- instance=modx_modules[module](xml,
- s.module_id,
- ajax_url=ajax_url,
- state=s.state,
- track_function = make_track_function(request),
- render_function = None)
+ instance=courseware.modules.get_module_class(module)(xml,
+ s.module_id,
+ ajax_url=ajax_url,
+ state=s.state,
+ track_function = make_track_function(request),
+ render_function = None)
# Let the module handle the AJAX
ajax_return=instance.handle_ajax(dispatch, request.POST)
# Save the state back to the database
@@ -100,7 +86,7 @@ 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
module_type=xml_module.tag
- module_class=modx_modules[module_type]
+ module_class=courseware.modules.get_module_class(module_type)
module_id=xml_module.get('id') #module_class.id_attribute) or ""
# Grab state from database
diff --git a/courseware/modules/__init__.py b/courseware/modules/__init__.py
index e69de29bb2..4c528144d2 100644
--- a/courseware/modules/__init__.py
+++ b/courseware/modules/__init__.py
@@ -0,0 +1,61 @@
+import os
+import os.path
+
+from django.conf import settings
+
+import capa_module
+import html_module
+import schematic_module
+import seq_module
+import template_module
+import vertical_module
+import video_module
+
+from courseware import content_parser
+
+# Import all files in modules directory, excluding backups (# and . in name)
+# and __init__
+#
+# Stick them in a list
+# modx_module_list = []
+
+# for f in os.listdir(os.path.dirname(__file__)):
+# if f!='__init__.py' and \
+# f[-3:] == ".py" and \
+# "." not in f[:-3] \
+# and '#' not in f:
+# mod_path = 'courseware.modules.'+f[:-3]
+# mod = __import__(mod_path, fromlist = "courseware.modules")
+# if 'Module' in mod.__dict__:
+# modx_module_list.append(mod)
+
+#print modx_module_list
+modx_module_list = [capa_module, html_module, schematic_module, seq_module, template_module, vertical_module, video_module]
+#print modx_module_list
+
+# Convert list to a dictionary for lookup by tag
+modx_modules = {}
+for module in modx_module_list:
+ for tag in module.Module.get_xml_tags():
+ modx_modules[tag] = module.Module
+
+def get_module_class(tag):
+ ''' Given an XML tag (e.g. 'video'), return
+ the associated module (e.g. video_module.Module).
+ '''
+ return modx_modules[tag]
+
+def get_module_id(tag):
+ ''' Given an XML tag (e.g. 'video'), return
+ the default ID for that module (e.g. 'youtube_id')
+ '''
+ return modx_modules[tag].id_attribute
+
+def get_valid_tags():
+ return modx_modules.keys()
+
+def get_default_ids():
+ tags = get_valid_tags()
+ ids = map(get_module_id, tags)
+ return dict(zip(tags, ids))
+
diff --git a/courseware/modules/capa_module.py b/courseware/modules/capa_module.py
index 3161664a9e..378c322c42 100644
--- a/courseware/modules/capa_module.py
+++ b/courseware/modules/capa_module.py
@@ -26,15 +26,18 @@ import courseware.content_parser as content_parser
log = logging.getLogger("mitx.courseware")
-class LoncapaModule(XModule):
+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
prupose now. We can e.g .destroy and create the capa_problem on a
reset.
'''
- xml_tags = ["problem"]
+
id_attribute = "filename"
+ @classmethod
+ def get_xml_tags(c):
+ return ["problem"]
def get_state(self):
state = self.lcp.get_state()
diff --git a/courseware/modules/html_module.py b/courseware/modules/html_module.py
index f1ce14ec7e..92e29df938 100644
--- a/courseware/modules/html_module.py
+++ b/courseware/modules/html_module.py
@@ -7,14 +7,15 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
from lxml import etree
-class HtmlModule(XModule):
+class Module(XModule):
id_attribute = 'filename'
def get_state(self):
return json.dumps({ })
- def get_xml_tags():
- return "html"
+ @classmethod
+ def get_xml_tags(c):
+ return ["html"]
def get_html(self):
if self.filename==None:
diff --git a/courseware/modules/schematic_module.py b/courseware/modules/schematic_module.py
index 0312321b4d..e253f1acc6 100644
--- a/courseware/modules/schematic_module.py
+++ b/courseware/modules/schematic_module.py
@@ -6,14 +6,15 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
-class SchematicModule(XModule):
+class Module(XModule):
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
- def get_xml_tags():
- return "schematic"
+ @classmethod
+ def get_xml_tags(c):
+ return ["schematic"]
def get_html(self):
return ''.format(item_id=self.item_id)
diff --git a/courseware/modules/seq_module.py b/courseware/modules/seq_module.py
index 33c4088486..6ec74feace 100644
--- a/courseware/modules/seq_module.py
+++ b/courseware/modules/seq_module.py
@@ -13,7 +13,7 @@ from x_module import XModule
# OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem']
-class SequentialModule(XModule):
+class Module(XModule):
''' Layout module which lays out content in a temporal sequence
'''
id_attribute = 'id'
@@ -21,7 +21,8 @@ class SequentialModule(XModule):
def get_state(self):
return json.dumps({ 'position':self.position })
- def get_xml_tags():
+ @classmethod
+ def get_xml_tags(c):
return ["sequential", 'tab']
def get_html(self):
diff --git a/courseware/modules/template_module.py b/courseware/modules/template_module.py
new file mode 100644
index 0000000000..11778348e3
--- /dev/null
+++ b/courseware/modules/template_module.py
@@ -0,0 +1,29 @@
+import json
+import os
+
+## TODO: Abstract out from Django
+from django.conf import settings
+from mitxmako.shortcuts import render_to_response, render_to_string
+
+from x_module import XModule
+from lxml import etree
+
+class Module(XModule):
+ def get_state(self):
+ return json.dumps({ })
+
+ @classmethod
+ def get_xml_tags(c):
+ tags = os.listdir(settings.DATA_DIR+'/custom_tags')
+ return tags
+
+ def get_html(self):
+ return self.html
+
+ def __init__(self, xml, item_id, ajax_url=None, track_url=None, state=None, track_function=None, render_function = None):
+ XModule.__init__(self, xml, item_id, ajax_url, track_url, state, track_function, render_function)
+ xmltree = etree.fromstring(xml)
+ filename = xmltree.tag
+ params = dict(xmltree.items())
+# print params
+ self.html = render_to_string('custom_tags/'+filename, params)
diff --git a/courseware/modules/vertical_module.py b/courseware/modules/vertical_module.py
index 6939c75cc9..c068cb9a76 100644
--- a/courseware/modules/vertical_module.py
+++ b/courseware/modules/vertical_module.py
@@ -7,14 +7,15 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from x_module import XModule
from lxml import etree
-class VerticalModule(XModule):
+class Module(XModule):
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
- def get_xml_tags():
- return "vertical"
+ @classmethod
+ def get_xml_tags(c):
+ return ["vertical"]
def get_html(self):
return render_to_string('vert_module.html',{'items':self.contents})
diff --git a/courseware/modules/video_module.py b/courseware/modules/video_module.py
index b7e1e211dc..2063c18953 100644
--- a/courseware/modules/video_module.py
+++ b/courseware/modules/video_module.py
@@ -11,8 +11,8 @@ from x_module import XModule
log = logging.getLogger("mitx.courseware.modules")
-class VideoModule(XModule):
- #id_attribute = 'youtube'
+class Module(XModule):
+ id_attribute = 'youtube'
video_time = 0
def handle_ajax(self, dispatch, get):
@@ -28,9 +28,10 @@ class VideoModule(XModule):
log.debug(u"STATE POSITION {0}".format(self.position))
return json.dumps({ 'position':self.position })
- def get_xml_tags():
+ @classmethod
+ def get_xml_tags(c):
'''Tags in the courseware file guaranteed to correspond to the module'''
- return "video"
+ return ["video"]
def video_list(self):
l = self.youtube.split(',')
diff --git a/courseware/modules/x_module.py b/courseware/modules/x_module.py
index 4b45c4c7fc..4a379253c4 100644
--- a/courseware/modules/x_module.py
+++ b/courseware/modules/x_module.py
@@ -8,9 +8,10 @@ class XModule(object):
Initialized on access with __init__, first time with state=None, and
then with state
'''
- id_attribute='name' # An attribute guaranteed to be unique
+ id_attribute='id' # An attribute guaranteed to be unique
- def get_xml_tags():
+ @classmethod
+ def get_xml_tags(c):
''' Tags in the courseware file guaranteed to correspond to the module '''
return []
diff --git a/courseware/tests.py b/courseware/tests.py
index 501deb776c..66a7ef181e 100644
--- a/courseware/tests.py
+++ b/courseware/tests.py
@@ -1,16 +1,36 @@
-"""
-This file demonstrates writing tests using the unittest module. These will pass
-when you run "manage.py test".
+import unittest
-Replace this with more appropriate tests for your application.
-"""
+import numpy
-from django.test import TestCase
+import courseware.modules
+import courseware.capa.calc as calc
+class ModelsTest(unittest.TestCase):
+ def setUp(self):
+ pass
-class SimpleTest(TestCase):
- def test_basic_addition(self):
- """
- Tests that 1 + 1 always equals 2.
- """
- self.assertEqual(1 + 1, 2)
+ def test_get_module_class(self):
+ vc = courseware.modules.get_module_class('video')
+ vc_str = ""
+ self.assertEqual(str(vc), vc_str)
+ video_id = courseware.modules.get_default_ids()['video']
+ self.assertEqual(video_id, 'youtube')
+
+ def test_calc(self):
+ variables={'R1':2.0, 'R3':4.0}
+ functions={'sin':numpy.sin, 'cos':numpy.cos}
+
+ self.assertEqual(calc.evaluator(variables, functions, "10000||sin(7+5)-6k"), 4000.0)
+ self.assertEqual(calc.evaluator({'R1': 2.0, 'R3':4.0}, {}, "13"), 13)
+ self.assertEqual(calc.evaluator(variables, functions, "13"), 13)
+ self.assertEqual(calc.evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5"), 5)
+ self.assertEqual(calc.evaluator({},{}, "-1"), -1)
+ self.assertEqual(calc.evaluator({},{}, "-0.33"), -.33)
+ self.assertEqual(calc.evaluator({},{}, "-.33"), -.33)
+ exception_happened = False
+ try:
+ evaluator({},{}, "5+7 QWSEKO")
+ except:
+ exception_happened = True
+ self.assertTrue(exception_happened)
+