Commit aa55c4ee by Your Name

Merge branch 'master' into feature/kevin/edit_commentable_id

parents f67e8ed1 01f5f592
...@@ -38,6 +38,7 @@ import calc ...@@ -38,6 +38,7 @@ import calc
from correctmap import CorrectMap from correctmap import CorrectMap
import eia import eia
import inputtypes import inputtypes
import customrender
from util import contextualize_text, convert_files_to_filenames from util import contextualize_text, convert_files_to_filenames
import xqueue_interface import xqueue_interface
...@@ -47,23 +48,8 @@ import responsetypes ...@@ -47,23 +48,8 @@ import responsetypes
# dict of tagname, Response Class -- this should come from auto-registering # dict of tagname, Response Class -- this should come from auto-registering
response_tag_dict = dict([(x.response_tag, x) for x in responsetypes.__all__]) response_tag_dict = dict([(x.response_tag, x) for x in responsetypes.__all__])
# Different ways students can input code
entry_types = ['textline',
'schematic',
'textbox',
'imageinput',
'optioninput',
'choicegroup',
'radiogroup',
'checkboxgroup',
'filesubmission',
'javascriptinput',
'crystallography',
'chemicalequationinput',
'vsepr_input']
# extra things displayed after "show answers" is pressed # extra things displayed after "show answers" is pressed
solution_types = ['solution'] solution_tags = ['solution']
# these get captured as student responses # these get captured as student responses
response_properties = ["codeparam", "responseparam", "answer"] response_properties = ["codeparam", "responseparam", "answer"]
...@@ -309,7 +295,7 @@ class LoncapaProblem(object): ...@@ -309,7 +295,7 @@ class LoncapaProblem(object):
answer_map.update(results) answer_map.update(results)
# include solutions from <solution>...</solution> stanzas # include solutions from <solution>...</solution> stanzas
for entry in self.tree.xpath("//" + "|//".join(solution_types)): for entry in self.tree.xpath("//" + "|//".join(solution_tags)):
answer = etree.tostring(entry) answer = etree.tostring(entry)
if answer: if answer:
answer_map[entry.get('id')] = contextualize_text(answer, self.context) answer_map[entry.get('id')] = contextualize_text(answer, self.context)
...@@ -487,7 +473,7 @@ class LoncapaProblem(object): ...@@ -487,7 +473,7 @@ class LoncapaProblem(object):
problemid = problemtree.get('id') # my ID problemid = problemtree.get('id') # my ID
if problemtree.tag in inputtypes.registered_input_tags(): if problemtree.tag in inputtypes.registry.registered_tags():
# If this is an inputtype subtree, let it render itself. # If this is an inputtype subtree, let it render itself.
status = "unsubmitted" status = "unsubmitted"
msg = '' msg = ''
...@@ -513,7 +499,7 @@ class LoncapaProblem(object): ...@@ -513,7 +499,7 @@ class LoncapaProblem(object):
'hint': hint, 'hint': hint,
'hintmode': hintmode,}} 'hintmode': hintmode,}}
input_type_cls = inputtypes.get_class_for_tag(problemtree.tag) input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
the_input = input_type_cls(self.system, problemtree, state) the_input = input_type_cls(self.system, problemtree, state)
return the_input.get_html() return the_input.get_html()
...@@ -521,9 +507,15 @@ class LoncapaProblem(object): ...@@ -521,9 +507,15 @@ class LoncapaProblem(object):
if problemtree in self.responders: if problemtree in self.responders:
return self.responders[problemtree].render_html(self._extract_html) return self.responders[problemtree].render_html(self._extract_html)
# let each custom renderer render itself:
if problemtree.tag in customrender.registry.registered_tags():
renderer_class = customrender.registry.get_class_for_tag(problemtree.tag)
renderer = renderer_class(self.system, problemtree)
return renderer.get_html()
# otherwise, render children recursively, and copy over attributes
tree = etree.Element(problemtree.tag) tree = etree.Element(problemtree.tag)
for item in problemtree: for item in problemtree:
# render child recursively
item_xhtml = self._extract_html(item) item_xhtml = self._extract_html(item)
if item_xhtml is not None: if item_xhtml is not None:
tree.append(item_xhtml) tree.append(item_xhtml)
...@@ -560,11 +552,12 @@ class LoncapaProblem(object): ...@@ -560,11 +552,12 @@ class LoncapaProblem(object):
response_id += 1 response_id += 1
answer_id = 1 answer_id = 1
input_tags = inputtypes.registry.registered_tags()
inputfields = tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x inputfields = tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x
for x in (entry_types + solution_types)]), for x in (input_tags + solution_tags)]),
id=response_id_str) id=response_id_str)
# assign one answer_id for each entry_type or solution_type # assign one answer_id for each input type or solution type
for entry in inputfields: for entry in inputfields:
entry.attrib['response_id'] = str(response_id) entry.attrib['response_id'] = str(response_id)
entry.attrib['answer_id'] = str(answer_id) entry.attrib['answer_id'] = str(answer_id)
......
"""
This has custom renderers: classes that know how to render certain problem tags (e.g. <math> and
<solution>) to html.
These tags do not have state, so they just get passed the system (for access to render_template),
and the xml element.
"""
from registry import TagRegistry
import logging
import re
import shlex # for splitting quoted strings
import json
from lxml import etree
import xml.sax.saxutils as saxutils
from registry import TagRegistry
log = logging.getLogger('mitx.' + __name__)
registry = TagRegistry()
#-----------------------------------------------------------------------------
class MathRenderer(object):
tags = ['math']
def __init__(self, system, xml):
'''
Render math using latex-like formatting.
Examples:
<math>$\displaystyle U(r)=4 U_0 $</math>
<math>$r_0$</math>
We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline]
TODO: use shorter tags (but this will require converting problem XML files!)
'''
self.system = system
self.xml = xml
mathstr = re.sub('\$(.*)\$', r'[mathjaxinline]\1[/mathjaxinline]', xml.text)
mtag = 'mathjax'
if not r'\displaystyle' in mathstr:
mtag += 'inline'
else:
mathstr = mathstr.replace(r'\displaystyle', '')
self.mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag)
def get_html(self):
"""
Return the contents of this tag, rendered to html, as an etree element.
"""
# TODO: why are there nested html tags here?? Why are there html tags at all, in fact?
html = '<html><html>%s</html><html>%s</html></html>' % (
self.mathstr, saxutils.escape(self.xml.tail))
try:
xhtml = etree.XML(html)
except Exception as err:
if self.system.DEBUG:
msg = '<html><div class="inline-error"><p>Error %s</p>' % (
str(err).replace('<', '&lt;'))
msg += ('<p>Failed to construct math expression from <pre>%s</pre></p>' %
html.replace('<', '&lt;'))
msg += "</div></html>"
log.error(msg)
return etree.XML(msg)
else:
raise
return xhtml
registry.register(MathRenderer)
#-----------------------------------------------------------------------------
class SolutionRenderer(object):
'''
A solution is just a <span>...</span> which is given an ID, that is used for displaying an
extended answer (a problem "solution") after "show answers" is pressed.
Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an
ajax call.
'''
tags = ['solution']
def __init__(self, system, xml):
self.system = system
self.id = xml.get('id')
def get_html(self):
context = {'id': self.id}
html = self.system.render_template("solutionspan.html", context)
return etree.XML(html)
registry.register(SolutionRenderer)
class TagRegistry(object):
"""
A registry mapping tags to handlers.
(A dictionary with some extra error checking.)
"""
def __init__(self):
self._mapping = {}
def register(self, cls):
"""
Register cls as a supported tag type. It is expected to define cls.tags as a list of tags
that it implements.
If an already-registered type has registered one of those tags, will raise ValueError.
If there are no tags in cls.tags, will also raise ValueError.
"""
# Do all checks and complain before changing any state.
if len(cls.tags) == 0:
raise ValueError("No tags specified for class {0}".format(cls.__name__))
for t in cls.tags:
if t in self._mapping:
other_cls = self._mapping[t]
if cls == other_cls:
# registering the same class multiple times seems silly, but ok
continue
raise ValueError("Tag {0} already registered by class {1}."
" Can't register for class {2}"
.format(t, other_cls.__name__, cls.__name__))
# Ok, should be good to change state now.
for t in cls.tags:
self._mapping[t] = cls
def registered_tags(self):
"""
Get a list of all the tags that have been registered.
"""
return self._mapping.keys()
def get_class_for_tag(self, tag):
"""
For any tag in registered_tags(), returns the corresponding class. Otherwise, will raise
KeyError.
"""
return self._mapping[tag]
<form class="choicegroup capa_inputtype" id="inputtype_${id}"> <form class="choicegroup capa_inputtype" id="inputtype_${id}">
<div class="indicator_container"> <div class="indicator_container">
% if state == 'unsubmitted': % if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span> <span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct': % elif status == 'correct':
<span class="correct" id="status_${id}"></span> <span class="correct" id="status_${id}"></span>
% elif state == 'incorrect': % elif status == 'incorrect':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% elif state == 'incomplete': % elif status == 'incomplete':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% endif % endif
</div> </div>
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
>${value|h}</textarea> >${value|h}</textarea>
<div class="grader-status"> <div class="grader-status">
% if state == 'unsubmitted': % if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span> <span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
% elif state == 'correct': % elif status == 'correct':
<span class="correct" id="status_${id}">Correct</span> <span class="correct" id="status_${id}">Correct</span>
% elif state == 'incorrect': % elif status == 'incorrect':
<span class="incorrect" id="status_${id}">Incorrect</span> <span class="incorrect" id="status_${id}">Incorrect</span>
% elif state == 'queued': % elif status == 'queued':
<span class="processing" id="status_${id}">Queued</span> <span class="processing" id="status_${id}">Queued</span>
<span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span> <span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
% endif % endif
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<div style="display:none;" name="${hidden}" inputid="input_${id}" /> <div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif % endif
<p class="debug">${state}</p> <p class="debug">${status}</p>
</div> </div>
<span id="answer_${id}"></span> <span id="answer_${id}"></span>
......
<% doinline = "inline" if inline else "" %> <section id="inputtype_${id}" class="capa_inputtype" >
<section id="textinput_${id}" class="textinput ${doinline}" >
<div id="holder" style="width:${width};height:${height}"></div> <div id="holder" style="width:${width};height:${height}"></div>
<div class="script_placeholder" data-src="/static/js/raphael.js"></div><div class="script_placeholder" data-src="/static/js/sylvester.js"></div><div class="script_placeholder" data-src="/static/js/underscore-min.js"></div> <div class="script_placeholder" data-src="/static/js/raphael.js"></div>
<div class="script_placeholder" data-src="/static/js/sylvester.js"></div>
<div class="script_placeholder" data-src="/static/js/underscore-min.js"></div>
<div class="script_placeholder" data-src="/static/js/crystallography.js"></div> <div class="script_placeholder" data-src="/static/js/crystallography.js"></div>
% if state == 'unsubmitted': % if status == 'unsubmitted':
<div class="unanswered ${doinline}" id="status_${id}"> <div class="unanswered" id="status_${id}">
% elif state == 'correct': % elif status == 'correct':
<div class="correct ${doinline}" id="status_${id}"> <div class="correct" id="status_${id}">
% elif state == 'incorrect': % elif status == 'incorrect':
<div class="incorrect ${doinline}" id="status_${id}"> <div class="incorrect" id="status_${id}">
% elif state == 'incomplete': % elif status == 'incomplete':
<div class="incorrect ${doinline}" id="status_${id}"> <div class="incorrect" id="status_${id}">
% endif % endif
% if hidden: % if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" /> <div style="display:none;" name="${hidden}" inputid="input_${id}" />
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
/> />
<p class="status"> <p class="status">
% if state == 'unsubmitted': % if status == 'unsubmitted':
unanswered unanswered
% elif state == 'correct': % elif status == 'correct':
correct correct
% elif state == 'incorrect': % elif status == 'incorrect':
incorrect incorrect
% elif state == 'incomplete': % elif status == 'incomplete':
incomplete incomplete
% endif % endif
</p> </p>
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
% if msg: % if msg:
<span class="message">${msg|n}</span> <span class="message">${msg|n}</span>
% endif % endif
% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden: % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div> </div>
% endif % endif
</section> </section>
<section id="filesubmission_${id}" class="filesubmission"> <section id="filesubmission_${id}" class="filesubmission">
<div class="grader-status file"> <div class="grader-status file">
% if state == 'unsubmitted': % if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span> <span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
% elif state == 'correct': % elif status == 'correct':
<span class="correct" id="status_${id}">Correct</span> <span class="correct" id="status_${id}">Correct</span>
% elif state == 'incorrect': % elif status == 'incorrect':
<span class="incorrect" id="status_${id}">Incorrect</span> <span class="incorrect" id="status_${id}">Incorrect</span>
% elif state == 'queued': % elif status == 'queued':
<span class="processing" id="status_${id}">Queued</span> <span class="processing" id="status_${id}">Queued</span>
<span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span> <span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
% endif % endif
<p class="debug">${state}</p> <p class="debug">${status}</p>
<input type="file" name="input_${id}" id="input_${id}" value="${value}" multiple="multiple" data-required_files="${required_files}" data-allowed_files="${allowed_files}"/> <input type="file" name="input_${id}" id="input_${id}" value="${value}" multiple="multiple" data-required_files="${required_files}" data-allowed_files="${allowed_files}"/>
</div> </div>
......
...@@ -4,13 +4,13 @@ ...@@ -4,13 +4,13 @@
<img src="/static/green-pointer.png" id="cross_${id}" style="position: absolute;top: ${gy}px;left: ${gx}px;" /> <img src="/static/green-pointer.png" id="cross_${id}" style="position: absolute;top: ${gy}px;left: ${gx}px;" />
</div> </div>
% if state == 'unsubmitted': % if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span> <span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct': % elif status == 'correct':
<span class="correct" id="status_${id}"></span> <span class="correct" id="status_${id}"></span>
% elif state == 'incorrect': % elif status == 'incorrect':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% elif state == 'incomplete': % elif status == 'incomplete':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% endif % endif
</span> </span>
...@@ -18,13 +18,13 @@ ...@@ -18,13 +18,13 @@
<textarea style="display:none" id="input_${id}_fromjs" name="input_${id}_fromjs"></textarea> <textarea style="display:none" id="input_${id}_fromjs" name="input_${id}_fromjs"></textarea>
% endif % endif
% if state == 'unsubmitted': % if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span> <span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct': % elif status == 'correct':
<span class="correct" id="status_${id}"></span> <span class="correct" id="status_${id}"></span>
% elif state == 'incorrect': % elif status == 'incorrect':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% elif state == 'incomplete': % elif status == 'incomplete':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% endif % endif
% if msg: % if msg:
......
...@@ -12,13 +12,13 @@ ...@@ -12,13 +12,13 @@
<span id="answer_${id}"></span> <span id="answer_${id}"></span>
% if state == 'unsubmitted': % if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span> <span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct': % elif status == 'correct':
<span class="correct" id="status_${id}"></span> <span class="correct" id="status_${id}"></span>
% elif state == 'incorrect': % elif status == 'incorrect':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% elif state == 'incomplete': % elif status == 'incomplete':
<span class="incorrect" id="status_${id}"></span> <span class="incorrect" id="status_${id}"></span>
% endif % endif
</form> </form>
...@@ -12,13 +12,13 @@ ...@@ -12,13 +12,13 @@
</script> </script>
<span id="answer_${id}"></span> <span id="answer_${id}"></span>
% if state == 'unsubmitted': % if status == 'unsubmitted':
<span class="ui-icon ui-icon-bullet" style="display:inline-block;" id="status_${id}"></span> <span class="ui-icon ui-icon-bullet" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct': % elif status == 'correct':
<span class="ui-icon ui-icon-check" style="display:inline-block;" id="status_${id}"></span> <span class="ui-icon ui-icon-check" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'incorrect': % elif status == 'incorrect':
<span class="ui-icon ui-icon-close" style="display:inline-block;" id="status_${id}"></span> <span class="ui-icon ui-icon-close" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'incomplete': % elif status == 'incomplete':
<span class="ui-icon ui-icon-close" style="display:inline-block;" id="status_${id}"></span> <span class="ui-icon ui-icon-close" style="display:inline-block;" id="status_${id}"></span>
% endif % endif
</span> </span>
......
###
### version of textline.html which does dynamic math
###
<section class="text-input-dynamath capa_inputtype" id="inputtype_${id}">
% if preprocessor is not None:
<div class="text-input-dynamath_data" data-preprocessor="${preprocessor['class_name']}"/>
<div class="script_placeholder" data-src="${preprocessor['script_src']}"/>
% endif
% if state == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif state == 'correct':
<div class="correct" id="status_${id}">
% elif state == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif state == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<input type="text" name="input_${id}" id="input_${id}" value="${value}" class="math" size="${size if size else ''}"
% if hidden:
style="display:none;"
% endif
/>
<p class="status">
% if state == 'unsubmitted':
unanswered
% elif state == 'correct':
correct
% elif state == 'incorrect':
incorrect
% elif state == 'incomplete':
incomplete
% endif
</p>
<p id="answer_${id}" class="answer"></p>
<div id="display_${id}" class="equation">`{::}`</div>
</div>
<textarea style="display:none" id="input_${id}_dynamath" name="input_${id}_dynamath"> </textarea>
% if msg:
<span class="message">${msg|n}</span>
% endif
</section>
<% doinline = "inline" if inline else "" %> <% doinline = "inline" if inline else "" %>
<section id="textinput_${id}" class="textinput ${doinline}" > <section id="inputtype_${id}" class="${'text-input-dynamath' if do_math else ''} capa_inputtype ${doinline}" >
% if state == 'unsubmitted':
% if preprocessor is not None:
<div class="text-input-dynamath_data" data-preprocessor="${preprocessor['class_name']}"/>
<div class="script_placeholder" data-src="${preprocessor['script_src']}"/>
% endif
% if status == 'unsubmitted':
<div class="unanswered ${doinline}" id="status_${id}"> <div class="unanswered ${doinline}" id="status_${id}">
% elif state == 'correct': % elif status == 'correct':
<div class="correct ${doinline}" id="status_${id}"> <div class="correct ${doinline}" id="status_${id}">
% elif state == 'incorrect': % elif status == 'incorrect':
<div class="incorrect ${doinline}" id="status_${id}"> <div class="incorrect ${doinline}" id="status_${id}">
% elif state == 'incomplete': % elif status == 'incomplete':
<div class="incorrect ${doinline}" id="status_${id}"> <div class="incorrect ${doinline}" id="status_${id}">
% endif % endif
% if hidden: % if hidden:
...@@ -15,32 +21,44 @@ ...@@ -15,32 +21,44 @@
% endif % endif
<input type="text" name="input_${id}" id="input_${id}" value="${value}" <input type="text" name="input_${id}" id="input_${id}" value="${value}"
% if size: % if do_math:
size="${size}" class="math"
% endif % endif
% if hidden: % if size:
style="display:none;" size="${size}"
% endif % endif
% if hidden:
style="display:none;"
% endif
/> />
<p class="status"> <p class="status">
% if state == 'unsubmitted': % if status == 'unsubmitted':
unanswered unanswered
% elif state == 'correct': % elif status == 'correct':
correct correct
% elif state == 'incorrect': % elif status == 'incorrect':
incorrect incorrect
% elif state == 'incomplete': % elif status == 'incomplete':
incomplete incomplete
% endif % endif
</p> </p>
<p id="answer_${id}" class="answer"></p> <p id="answer_${id}" class="answer"></p>
% if do_math:
<div id="display_${id}" class="equation">`{::}`</div>
<textarea style="display:none" id="input_${id}_dynamath" name="input_${id}_dynamath">
</textarea>
% endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
% if msg: % if msg:
<span class="message">${msg|n}</span> <span class="message">${msg|n}</span>
% endif % endif
% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden:
</div>
% endif
</section> </section>
<% doinline = "inline" if inline else "" %> <section id="inputtype_${id}" class="capa_inputtype" >
<section id="textinput_${id}" class="textinput ${doinline}" >
<table><tr><td height='600'> <table><tr><td height='600'>
<div id="vsepr_div_${id}" style="position:relative;" data-molecules="${molecules}" data-geometries="${geometries}"> <div id="vsepr_div_${id}" style="position:relative;" data-molecules="${molecules}" data-geometries="${geometries}">
<canvas id="vsepr_canvas_${id}" width="${width}" height="${height}"> <canvas id="vsepr_canvas_${id}" width="${width}" height="${height}">
...@@ -13,36 +11,28 @@ ...@@ -13,36 +11,28 @@
<div class="script_placeholder" data-src="/static/js/vsepr/vsepr.js"></div> <div class="script_placeholder" data-src="/static/js/vsepr/vsepr.js"></div>
% if state == 'unsubmitted': % if status == 'unsubmitted':
<div class="unanswered ${doinline}" id="status_${id}"> <div class="unanswered" id="status_${id}">
% elif state == 'correct': % elif status == 'correct':
<div class="correct ${doinline}" id="status_${id}"> <div class="correct" id="status_${id}">
% elif state == 'incorrect': % elif status == 'incorrect':
<div class="incorrect ${doinline}" id="status_${id}"> <div class="incorrect" id="status_${id}">
% elif state == 'incomplete': % elif status == 'incomplete':
<div class="incorrect ${doinline}" id="status_${id}"> <div class="incorrect" id="status_${id}">
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif % endif
<input type="text" name="input_${id}" id="input_${id}" value="${value}" <input type="text" name="input_${id}" id="input_${id}" value="${value}"
% if size: style="display:none;"
size="${size}"
% endif
% if hidden:
style="display:none;"
% endif
/> />
<p class="status"> <p class="status">
% if state == 'unsubmitted': % if status == 'unsubmitted':
unanswered unanswered
% elif state == 'correct': % elif status == 'correct':
correct correct
% elif state == 'incorrect': % elif status == 'incorrect':
incorrect incorrect
% elif state == 'incomplete': % elif status == 'incomplete':
incomplete incomplete
% endif % endif
</p> </p>
...@@ -52,7 +42,7 @@ ...@@ -52,7 +42,7 @@
% if msg: % if msg:
<span class="message">${msg|n}</span> <span class="message">${msg|n}</span>
% endif % endif
% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden: % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div> </div>
% endif % endif
</section> </section>
...@@ -4,13 +4,23 @@ import os ...@@ -4,13 +4,23 @@ import os
from mock import Mock from mock import Mock
import xml.sax.saxutils as saxutils
TEST_DIR = os.path.dirname(os.path.realpath(__file__)) TEST_DIR = os.path.dirname(os.path.realpath(__file__))
def tst_render_template(template, context):
"""
A test version of render to template. Renders to the repr of the context, completely ignoring
the template name. To make the output valid xml, quotes the content, and wraps it in a <div>
"""
return '<div>{0}</div>'.format(saxutils.escape(repr(context)))
test_system = Mock( test_system = Mock(
ajax_url='courses/course_id/modx/a_location', ajax_url='courses/course_id/modx/a_location',
track_function=Mock(), track_function=Mock(),
get_module=Mock(), get_module=Mock(),
render_template=Mock(), render_template=tst_render_template,
replace_urls=Mock(), replace_urls=Mock(),
user=Mock(), user=Mock(),
filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")), filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")),
......
from lxml import etree
import unittest
import xml.sax.saxutils as saxutils
from . import test_system
from capa import customrender
# just a handy shortcut
lookup_tag = customrender.registry.get_class_for_tag
def extract_context(xml):
"""
Given an xml element corresponding to the output of test_system.render_template, get back the
original context
"""
return eval(xml.text)
def quote_attr(s):
return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
class HelperTest(unittest.TestCase):
'''
Make sure that our helper function works!
'''
def check(self, d):
xml = etree.XML(test_system.render_template('blah', d))
self.assertEqual(d, extract_context(xml))
def test_extract_context(self):
self.check({})
self.check({1, 2})
self.check({'id', 'an id'})
self.check({'with"quote', 'also"quote'})
class SolutionRenderTest(unittest.TestCase):
'''
Make sure solutions render properly.
'''
def test_rendering(self):
solution = 'To compute unicorns, count them.'
xml_str = """<solution id="solution_12">{s}</solution>""".format(s=solution)
element = etree.fromstring(xml_str)
renderer = lookup_tag('solution')(test_system, element)
self.assertEqual(renderer.id, 'solution_12')
# our test_system "renders" templates to a div with the repr of the context
xml = renderer.get_html()
context = extract_context(xml)
self.assertEqual(context, {'id' : 'solution_12'})
class MathRenderTest(unittest.TestCase):
'''
Make sure math renders properly.
'''
def check_parse(self, latex_in, mathjax_out):
xml_str = """<math>{tex}</math>""".format(tex=latex_in)
element = etree.fromstring(xml_str)
renderer = lookup_tag('math')(test_system, element)
self.assertEqual(renderer.mathstr, mathjax_out)
def test_parsing(self):
self.check_parse('$abc$', '[mathjaxinline]abc[/mathjaxinline]')
self.check_parse('$abc', '$abc')
self.check_parse(r'$\displaystyle 2+2$', '[mathjax] 2+2[/mathjax]')
# NOTE: not testing get_html yet because I don't understand why it's doing what it's doing.
...@@ -247,7 +247,8 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -247,7 +247,8 @@ class CourseDescriptor(SequenceDescriptor):
@property @property
def start_date_text(self): def start_date_text(self):
return time.strftime("%b %d, %Y", self.start) displayed_start = self._try_parse_time('advertised_start') or self.start
return time.strftime("%b %d, %Y", displayed_start)
# An extra property is used rather than the wiki_slug/number because # An extra property is used rather than the wiki_slug/number because
# there are courses that change the number for different runs. This allows # there are courses that change the number for different runs. This allows
......
...@@ -355,6 +355,34 @@ div.video { ...@@ -355,6 +355,34 @@ div.video {
} }
} }
a.quality_control {
background: url(../images/hd.png) center no-repeat;
border-right: 1px solid #000;
@include box-shadow(1px 0 0 #555, inset 1px 0 0 #555);
color: #797979;
display: block;
float: left;
line-height: 46px; //height of play pause buttons
margin-left: 0;
padding: 0 lh(.5);
text-indent: -9999px;
@include transition();
width: 30px;
&:hover {
background-color: #444;
color: #fff;
text-decoration: none;
}
&.active {
background-color: #F44;
color: #0ff;
text-decoration: none;
}
}
a.hide-subtitles { a.hide-subtitles {
background: url('../images/cc.png') center no-repeat; background: url('../images/cc.png') center no-repeat;
color: #797979; color: #797979;
......
...@@ -22,7 +22,7 @@ class @VideoCaption extends Subview ...@@ -22,7 +22,7 @@ class @VideoCaption extends Subview
""" """
@$('.video-controls .secondary-controls').append """ @$('.video-controls .secondary-controls').append """
<a href="#" class="hide-subtitles" title="Turn off captions">Captions</a> <a href="#" class="hide-subtitles" title="Turn off captions">Captions</a>
""" """#"
@$('.subtitles').css maxHeight: @$('.video-wrapper').height() - 5 @$('.subtitles').css maxHeight: @$('.video-wrapper').height() - 5
@fetchCaption() @fetchCaption()
...@@ -144,7 +144,7 @@ class @VideoCaption extends Subview ...@@ -144,7 +144,7 @@ class @VideoCaption extends Subview
@el.removeClass('closed') @el.removeClass('closed')
@scrollCaption() @scrollCaption()
$.cookie('hide_captions', hide_captions, expires: 3650, path: '/') $.cookie('hide_captions', hide_captions, expires: 3650, path: '/')
captionHeight: -> captionHeight: ->
if @el.hasClass('fullscreen') if @el.hasClass('fullscreen')
$(window).height() - @$('.video-controls').height() $(window).height() - @$('.video-controls').height()
......
...@@ -16,7 +16,7 @@ class @VideoControl extends Subview ...@@ -16,7 +16,7 @@ class @VideoControl extends Subview
<a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a> <a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>
</div> </div>
</div> </div>
""" """#"
unless onTouchBasedDevice() unless onTouchBasedDevice()
@$('.video_control').addClass('play').html('Play') @$('.video_control').addClass('play').html('Play')
......
...@@ -9,6 +9,7 @@ class @VideoPlayer extends Subview ...@@ -9,6 +9,7 @@ class @VideoPlayer extends Subview
bind: -> bind: ->
$(@control).bind('play', @play) $(@control).bind('play', @play)
.bind('pause', @pause) .bind('pause', @pause)
$(@qualityControl).bind('changeQuality', @handlePlaybackQualityChange)
$(@caption).bind('seek', @onSeek) $(@caption).bind('seek', @onSeek)
$(@speedControl).bind('speedChange', @onSpeedChange) $(@speedControl).bind('speedChange', @onSpeedChange)
$(@progressSlider).bind('seek', @onSeek) $(@progressSlider).bind('seek', @onSeek)
...@@ -25,6 +26,7 @@ class @VideoPlayer extends Subview ...@@ -25,6 +26,7 @@ class @VideoPlayer extends Subview
render: -> render: ->
@control = new VideoControl el: @$('.video-controls') @control = new VideoControl el: @$('.video-controls')
@qualityControl = new VideoQualityControl el: @$('.secondary-controls')
@caption = new VideoCaption @caption = new VideoCaption
el: @el el: @el
youtubeId: @video.youtubeId('1.0') youtubeId: @video.youtubeId('1.0')
...@@ -41,10 +43,12 @@ class @VideoPlayer extends Subview ...@@ -41,10 +43,12 @@ class @VideoPlayer extends Subview
rel: 0 rel: 0
showinfo: 0 showinfo: 0
enablejsapi: 1 enablejsapi: 1
modestbranding: 1
videoId: @video.youtubeId() videoId: @video.youtubeId()
events: events:
onReady: @onReady onReady: @onReady
onStateChange: @onStateChange onStateChange: @onStateChange
onPlaybackQualityChange: @onPlaybackQualityChange
@caption.hideCaptions(@['video'].hide_captions) @caption.hideCaptions(@['video'].hide_captions)
addToolTip: -> addToolTip: ->
...@@ -53,7 +57,7 @@ class @VideoPlayer extends Subview ...@@ -53,7 +57,7 @@ class @VideoPlayer extends Subview
my: 'top right' my: 'top right'
at: 'top center' at: 'top center'
onReady: => onReady: (event) =>
unless onTouchBasedDevice() unless onTouchBasedDevice()
$('.video-load-complete:first').data('video').player.play() $('.video-load-complete:first').data('video').player.play()
...@@ -68,6 +72,13 @@ class @VideoPlayer extends Subview ...@@ -68,6 +72,13 @@ class @VideoPlayer extends Subview
when YT.PlayerState.ENDED when YT.PlayerState.ENDED
@onEnded() @onEnded()
onPlaybackQualityChange: (event, value) =>
quality = @player.getPlaybackQuality()
@qualityControl.onQualityChange(quality)
handlePlaybackQualityChange: (event, value) =>
@player.setPlaybackQuality(value)
onUnstarted: => onUnstarted: =>
@control.pause() @control.pause()
@caption.pause() @caption.pause()
......
class @VideoQualityControl extends Subview
initialize: ->
@quality = null;
bind: ->
@$('.quality_control').click @toggleQuality
render: ->
@el.append """
<a href="#" class="quality_control" title="HD">HD</a>
"""#"
onQualityChange: (value) ->
@quality = value
if @quality in ['hd720', 'hd1080', 'highres']
@el.addClass('active')
else
@el.removeClass('active')
toggleQuality: (event) =>
event.preventDefault()
if @quality in ['hd720', 'hd1080', 'highres']
newQuality = 'large'
else
newQuality = 'hd720'
$(@).trigger('changeQuality', newQuality)
\ No newline at end of file
...@@ -17,7 +17,7 @@ class @VideoVolumeControl extends Subview ...@@ -17,7 +17,7 @@ class @VideoVolumeControl extends Subview
<div class="volume-slider"></div> <div class="volume-slider"></div>
</div> </div>
</div> </div>
""" """#"
@slider = @$('.volume-slider').slider @slider = @$('.volume-slider').slider
orientation: "vertical" orientation: "vertical"
range: "min" range: "min"
......
...@@ -136,6 +136,14 @@ ...@@ -136,6 +136,14 @@
margin-bottom: 15px; margin-bottom: 15px;
} }
h4 {
font-size: 1.0em;
font-family: $sans-serif;
font-weight: 700;
margin-top: 25px;
margin-bottom: 10px;
}
ul { ul {
padding-left: 50px; padding-left: 50px;
} }
......
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-35248639-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
...@@ -21,20 +21,9 @@ ...@@ -21,20 +21,9 @@
<meta name="path_prefix" content="${MITX_ROOT_URL}"> <meta name="path_prefix" content="${MITX_ROOT_URL}">
% if not course: % if not course:
<script type="text/javascript"> <%include file="google_analytics.html" />
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-35248639-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
% endif % endif
</head> </head>
<body class="<%block name='bodyclass'/>"> <body class="<%block name='bodyclass'/>">
......
...@@ -7,7 +7,12 @@ ...@@ -7,7 +7,12 @@
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%block name="headextra">
<%include file="../google_analytics.html" />
</%block>
<%block name="js_extra"> <%block name="js_extra">
% if not registered: % if not registered:
%if user.is_authenticated(): %if user.is_authenticated():
## If the user is authenticated, clicking the enroll button just submits a form ## If the user is authenticated, clicking the enroll button just submits a form
......
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%block name="title"><title>Jobs</title></%block> <%block name="title"><title>Jobs</title></%block>
...@@ -50,12 +49,79 @@ ...@@ -50,12 +49,79 @@
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p> <p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p>
</div> </div>
</article> </article>
<article id="learning-designer" class="job">
<div class="inner-wrapper">
<h3>Learning Designer/Interaction Learning Designer </h3>
<p>The Learning Designer will work as part of the content and development team to plan, develop and deliver highly engaging and media rich online courses. The learning designer will be a flexible thinker, able to determine and apply sound pedagogical strategies to unique situations and a diverse set of academic disciplines. This is a 6-12 months contract opportunity.</p>
<h4>Specific Responsibilities include: </h4>
<ul>
<li>Work with producers, product developers and course staff on implementing instructional design approaches in the development of media and other course materials. </li>
<li>Articulate learning objectives and align them to content design strategy and assessments. </li>
<li>Write effective instructional text, and audio and video scripts. </li>
<li>Coordinate workflows with video and content development team</li>
<li>Identify best practices and share these with the course staff and faculty as needed. </li>
<li>Create course communication style guides. Train and coach teaching staff on best practices for communication and discussion management. </li>
<li>Develop use case guides as needed on the use of edX courseware and new technologies. </li>
<li>Serve as a liaison to instructional design teams located at X universities. </li>
<li>Design peer review processes to be used by learners in selected courses. </li>
<li>Ability to apply game-based learning theory and design into selected courses as appropriate.</li>
<li>Use learning analytics and metrics to inform course design and revision process. </li>
<li>Work closely with the Content Research Director on articulating best practices for MOOC teaching and learning and course design.</li>
<li>Assist in the development of pilot courses used for sponsored research initiatives. </li>
</ul>
<h4>Qualifications:</h4>
<p>Master's Degree in Educational Technology, Instructional Design or related field. Experience in higher education with additional experience in a start-up or research environment desirable. Excellent interpersonal and communication (written and verbal), project management, problem-solving and time management skills. The ability to be flexible with projects and to work on multiple courses essential. Ability to meet deadlines and manage expectations of constituents. Capacity to develop new and relevant technology skills. &nbsp;Experience using game theory design and learning analytics to inform instructional design decisions and strategy.</p>
<h4>Technical Skills:</h4>
<p>Video and screencasting experience. LMS Platform experience, xml, HTML, CSS, Adobe Design Suite, Camtasia or Captivate experience. Experience with web 2.0 collaboration tools.</p>
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p>
</div>
</article>
<article id="production-coordinator" class="job">
<div class="inner-wrapper">
<h3>Production Coordinator</h3>
<p>The Production Coordinator supports video editors and course staff in all video related tasks, such as ingesting footage, transcoding, tracking live dates, transcriptions, organizing project deliverables and archiving completed projects.</p>
<h4>Primary responsibilities:</h4>
<ul>
<li>organize, track, and manage video and associated assets across the video workflow</li>
<li>manage project data and spreadsheets</li>
<li>route incoming source footage, and apply metadata tags</li>
<li>run encoding/transcoding jobs </li>
<li>prepare and process associated video assets, such as slides and image files</li>
<li>manage the transcription process </li>
<ul type="circle">
<li>traffic files among project staff and video transcription services</li>
<li>coordinate transcript reviews with course staff</li>
<li>integrate transcripts in course pages</li>
</ul>
<li>other video-related tasks as assigned.</li>
</ul>
<br/>
<h4>Qualifications</h4>
<p>The ideal candidate for the Production Coordinator position will have</p>
<ul>
<li>relentless attention to detail</li>
<li>ability to communicate and collaborate effectively across the organization</li>
<li>knowledge and understanding of digital media production tools and processes</li>
<li>experience with compression techniques, image processing, and presentation software preferred</li>
<li>proficiency with standard office applications </li>
<ul type="circle">
<li>spreadsheets</li>
<li>word processing</li>
<li>presentation</li>
</ul>
<li>experience with web publishing, e.g., HTML, XML, CSS, a plus</li>
</ul>
<p>If you are interested in this position, please send an email to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p>
</div>
</article>
</section> </section>
<section class="jobs-sidebar"> <section class="jobs-sidebar">
<h2>Positions</h2> <h2>Positions</h2>
<nav> <nav>
<a href="#content-engineer">EdX Content Engineer</a> <a href="#content-engineer">EdX Content Engineer</a>
<a href="#platform-developer">Platform Developer</a> <a href="#platform-developer">Platform Developer</a>
<a href="#learning-designer">Learning Designer</a>
<a href="#production-coordinator">Production Coordinator</a>
</nav> </nav>
<h2>How to Apply</h2> <h2>How to Apply</h2>
<p>E-mail your resume, coverletter and any other materials to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p> <p>E-mail your resume, coverletter and any other materials to <a href="mailto:jobs@edx.org">jobs@edx.org</a></p>
......
...@@ -50,4 +50,5 @@ pygraphviz ...@@ -50,4 +50,5 @@ pygraphviz
-r repo-requirements.txt -r repo-requirements.txt
pil pil
nltk nltk
dogstatsd-python dogstatsd-python
\ No newline at end of file MySQL-python
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment