Commit 059b9f66 by Alexander Kryklia

poll and conditional finished

parent 50af0be1
...@@ -277,7 +277,6 @@ class Test_Render_Equations(unittest.TestCase): ...@@ -277,7 +277,6 @@ class Test_Render_Equations(unittest.TestCase):
def test_render9(self): def test_render9(self):
s = "5[Ni(NH3)4]^2+ + 5/2SO4^2-" s = "5[Ni(NH3)4]^2+ + 5/2SO4^2-"
#import ipdb; ipdb.set_trace()
out = render_to_html(s) out = render_to_html(s)
correct = u'<span class="math">5[Ni(NH<sub>3</sub>)<sub>4</sub>]<sup>2+</sup>+<sup>5</sup>&frasl;<sub>2</sub>SO<sub>4</sub><sup>2-</sup></span>' correct = u'<span class="math">5[Ni(NH<sub>3</sub>)<sub>4</sub>]<sup>2+</sup>+<sup>5</sup>&frasl;<sub>2</sub>SO<sub>4</sub><sup>2-</sup></span>'
log(out + ' ------- ' + correct, 'html') log(out + ' ------- ' + correct, 'html')
......
...@@ -28,6 +28,7 @@ setup( ...@@ -28,6 +28,7 @@ setup(
"image = xmodule.backcompat_module:TranslateCustomTagDescriptor", "image = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"error = xmodule.error_module:ErrorDescriptor", "error = xmodule.error_module:ErrorDescriptor",
"peergrading = xmodule.peer_grading_module:PeerGradingDescriptor", "peergrading = xmodule.peer_grading_module:PeerGradingDescriptor",
"poll_question = xmodule.poll_module:PollDescriptor",
"problem = xmodule.capa_module:CapaDescriptor", "problem = xmodule.capa_module:CapaDescriptor",
"problemset = xmodule.seq_module:SequenceDescriptor", "problemset = xmodule.seq_module:SequenceDescriptor",
"randomize = xmodule.randomize_module:RandomizeDescriptor", "randomize = xmodule.randomize_module:RandomizeDescriptor",
...@@ -44,8 +45,8 @@ setup( ...@@ -44,8 +45,8 @@ setup(
"static_tab = xmodule.html_module:StaticTabDescriptor", "static_tab = xmodule.html_module:StaticTabDescriptor",
"custom_tag_template = xmodule.raw_module:RawDescriptor", "custom_tag_template = xmodule.raw_module:RawDescriptor",
"about = xmodule.html_module:AboutDescriptor", "about = xmodule.html_module:AboutDescriptor",
"poll = xmodule.poll_module:PollDescriptor", "graphical_slider_tool = xmodule.gst_module:GraphicalSliderToolDescriptor,
"graphical_slider_tool = xmodule.gst_module:GraphicalSliderToolDescriptor", "wrapper = xmodule.wrapper_module:WrapperDescriptor",
] ],
} }
) )
...@@ -521,7 +521,6 @@ class CapaModule(XModule): ...@@ -521,7 +521,6 @@ class CapaModule(XModule):
answers = self.make_dict_of_responses(get) answers = self.make_dict_of_responses(get)
event_info['answers'] = convert_files_to_filenames(answers) event_info['answers'] = convert_files_to_filenames(answers)
# Too late. Cannot submit # Too late. Cannot submit
if self.closed(): if self.closed():
event_info['failure'] = 'closed' event_info['failure'] = 'closed'
......
"""Conditional module is the xmodule, which you can use for disabling
some xmodules by conditions.
"""
import json import json
import logging import logging
from lxml import etree
from pkg_resources import resource_string
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor from xmodule.seq_module import SequenceDescriptor
from xblock.core import String, Scope from xblock.core import String, Scope, List
from xmodule.modulestore.exceptions import ItemNotFoundError
from pkg_resources import resource_string
log = logging.getLogger('mitx.' + __name__) log = logging.getLogger('mitx.' + __name__)
class ConditionalModule(XModule): class ConditionalModule(XModule):
''' """
Blocks child module from showing unless certain conditions are met. Blocks child module from showing unless certain conditions are met.
Example: Example:
<conditional condition="require_completed" required="tag/url_name1&tag/url_name2"> <conditional sources="i4x://.../problem_1; i4x://.../problem_2" completed="True">
<show sources="i4x://.../test_6; i4x://.../Avi_resources"/>
<video url_name="secret_video" /> <video url_name="secret_video" />
</conditional> </conditional>
<conditional condition="require_attempted" required="tag/url_name1&tag/url_name2"> TODO string comparison
<video url_name="secret_video" /> multiple answer for every poll
</conditional>
''' """
js = {'coffee': [resource_string(__name__, 'js/src/conditional/display.coffee'), js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'),
resource_string(__name__, 'js/src/conditional/display.coffee'),
resource_string(__name__, 'js/src/collapsible.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'),
resource_string(__name__, 'js/src/javascript_loader.coffee'),
]} ]}
js_module_name = "Conditional" js_module_name = "Conditional"
css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]} css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]}
condition = String(help="Condition for this module", default='', scope=Scope.settings) contents = String(scope=Scope.content)
# Map
# key: <tag attribute in xml>
# value: <name of module attribute>
conditions_map = {
'poll_answer': 'poll_answer', # poll_question attr
'compeleted': 'is_competed', # capa_problem attr
'attempted': 'is_attempted', # capa_problem attr
'voted': 'voted' # poll_question attr
}
def _get_condition(self):
# Get first valid condition.
for xml_attr, attr_name in self.conditions_map.iteritems():
xml_value = self.descriptor.xml_attributes.get(xml_attr)
if xml_value:
return xml_value, attr_name
raise Exception('Error in conditional module: unknown condition "%s"'
% xml_attr)
def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): def is_condition_satisfied(self):
""" self.required_modules = [self.system.get_module(descriptor.location) for
In addition to the normal XModule init, provide: descriptor in self.descriptor.get_required_module_descriptors()]
self.condition = string describing condition required xml_value, attr_name = self._get_condition()
""" if xml_value and self.required_modules:
XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) for module in self.required_modules:
self.contents = None if not hasattr(module, attr_name):
self._get_required_modules() raise Exception('Error in conditional module: \
children = self.get_display_items() required module {module} has no {module_attr}'.format(
if children: module=module, module_attr=attr_name))
self.icon_class = children[0].get_icon_class()
#log.debug('conditional module required=%s' % self.required_modules_list)
def _get_required_modules(self):
self.required_modules = []
for descriptor in self.descriptor.get_required_module_descriptors():
module = self.system.get_module(descriptor)
self.required_modules.append(module)
#log.debug('required_modules=%s' % (self.required_modules))
def is_condition_satisfied(self): attr = getattr(module, attr_name)
self._get_required_modules() if callable(attr):
attr = attr()
if self.condition == 'require_completed': if xml_value != str(attr):
# all required modules must be completed, as determined by break
# the modules .is_completed() method else:
for module in self.required_modules: return True
#log.debug('in is_condition_satisfied; student_answers=%s' % module.lcp.student_answers) return False
#log.debug('in is_condition_satisfied; instance_state=%s' % module.instance_state)
if not hasattr(module, 'is_completed'):
raise Exception('Error in conditional module: required module %s has no .is_completed() method' % module)
if not module.is_completed():
log.debug('conditional module: %s not completed' % module)
return False
else:
log.debug('conditional module: %s IS completed' % module)
return True
elif self.condition == 'require_attempted':
# all required modules must be attempted, as determined by
# the modules .is_attempted() method
for module in self.required_modules:
if not hasattr(module, 'is_attempted'):
raise Exception('Error in conditional module: required module %s has no .is_attempted() method' % module)
if not module.is_attempted():
log.debug('conditional module: %s not attempted' % module)
return False
else:
log.debug('conditional module: %s IS attempted' % module)
return True
else:
raise Exception('Error in conditional module: unknown condition "%s"' % self.condition)
return True
def get_html(self): def get_html(self):
self.is_condition_satisfied() # Calculate html ids of dependencies
self.required_html_ids = [descriptor.location.html_id() for
descriptor in self.descriptor.get_required_module_descriptors()]
return self.system.render_template('conditional_ajax.html', { return self.system.render_template('conditional_ajax.html', {
'element_id': self.location.html_id(), 'element_id': self.location.html_id(),
'id': self.id, 'id': self.id,
'ajax_url': self.system.ajax_url, 'ajax_url': self.system.ajax_url,
'depends': ';'.join(self.required_html_ids)
}) })
def handle_ajax(self, dispatch, post): def handle_ajax(self, dispatch, post):
''' """This is called by courseware.moduleodule_render, to handle
This is called by courseware.module_render, to handle an AJAX call. an AJAX call.
''' """
#log.debug('conditional_module handle_ajax: dispatch=%s' % dispatch)
if not self.is_condition_satisfied(): if not self.is_condition_satisfied():
context = {'module': self} message = self.descriptor.xml_attributes.get('message')
html = self.system.render_template('conditional_module.html', context) context = {'module': self,
return json.dumps({'html': html}) 'message': message}
html = self.system.render_template('conditional_module.html',
context)
return json.dumps({'html': [html], 'passed': False,
'message': bool(message)})
if self.contents is None: if self.contents is None:
self.contents = [child.get_html() for child in self.get_display_items()] self.contents = [self.system.get_module(child_descriptor.location
).get_html()
for child_descriptor in self.descriptor.get_children()]
# for now, just deal with one child html = self.contents
html = self.contents[0] return json.dumps({'html': html, 'passed': True})
return json.dumps({'html': html})
class ConditionalDescriptor(SequenceDescriptor): class ConditionalDescriptor(SequenceDescriptor):
"""Descriptor for conditional xmodule."""
_tag_name = 'conditional'
module_class = ConditionalModule module_class = ConditionalModule
filename_extension = "xml" filename_extension = "xml"
...@@ -129,28 +131,66 @@ class ConditionalDescriptor(SequenceDescriptor): ...@@ -129,28 +131,66 @@ class ConditionalDescriptor(SequenceDescriptor):
stores_state = True stores_state = True
has_score = False has_score = False
required = String(help="List of required xmodule locations, separated by &", default='', scope=Scope.settings) show_tag_list = List(help="Poll answers", scope=Scope.content)
def __init__(self, *args, **kwargs): @staticmethod
super(ConditionalDescriptor, self).__init__(*args, **kwargs) def parse_sources(xml_element, system, return_descriptor=False):
"""Parse xml_element 'sources' attr and:
required_module_list = [tuple(x.split('/', 1)) for x in self.required.split('&')] if return_descriptor=True - return list of descriptors
self.required_module_locations = [] if return_descriptor=False - return list of lcoations
for rm in required_module_list: """
try: result = []
(tag, name) = rm sources = xml_element.get('sources')
except Exception as err: if sources:
msg = "Specification of required module in conditional is broken: %s" % self.required locations = [location.strip() for location in sources.split(';')]
log.warning(msg) for location in locations:
self.system.error_tracker(msg) # Check valid location url.
continue if Location.is_valid(location):
loc = self.location.dict() try:
loc['category'] = tag descriptor = system.load_item(location)
loc['name'] = name if return_descriptor:
self.required_module_locations.append(Location(loc)) result.append(descriptor)
log.debug('ConditionalDescriptor required_module_locations=%s' % self.required_module_locations) else:
result.append(location)
except ItemNotFoundError:
log.exception("Invalid module by location.")
return result
def get_required_module_descriptors(self): def get_required_module_descriptors(self):
"""Returns a list of XModuleDescritpor instances upon which this module depends, but are """Returns a list of XModuleDescritpor instances upon
not children of this module""" which this module depends.
return [self.system.load_item(loc) for loc in self.required_module_locations] """
return ConditionalDescriptor.parse_sources(
self.xml_attributes, self.system, True)
@classmethod
def definition_from_xml(cls, xml_object, system):
children = []
show_tag_list = []
for child in xml_object:
if child.tag == 'show':
location = ConditionalDescriptor.parse_sources(
child, system)
children.extend(location)
show_tag_list.extend(location)
else:
try:
descriptor = system.process_xml(etree.tostring(child))
module_url = descriptor.location.url()
children.append(module_url)
except:
log.exception("Unable to load child when parsing Conditional.")
return {'show_tag_list': show_tag_list}, children
def definition_to_xml(self, resource_fs):
xml_object = etree.Element(self._tag_name)
for child in self.get_children():
location = str(child.location)
if location in self.show_tag_list:
show_str = '<{tag_name} sources="{sources}" />'.format(
tag_name='show', sources=location)
xml_object.append(etree.fromstring(show_str))
else:
xml_object.append(
etree.fromstring(child.export_to_xml(resource_fs)))
return xml_object
section.poll_question {
@media print {
display: block;
width: auto;
padding: 0;
canvas, img {
page-break-inside: avoid;
}
}
.inline {
display: inline;
}
h3 {
margin-top: 0;
margin-bottom: 15px;
color: #fe57a1;
font-size: 1.9em;
&.problem-header {
section.staff {
margin-top: 30px;
font-size: 80%;
}
}
@media print {
display: block;
width: auto;
border-right: 0;
}
}
p {
text-align: justify;
font-weight: bold;
}
.poll_answer {
margin-bottom: 20px;
&.short {
clear: both;
}
.question {
height: auto;
clear: both;
min-height: 30px;
&.short {
clear: none;
width: 30%;
display: inline;
float: left;
}
.button {
-webkit-appearance: none;
-webkit-background-clip: padding-box;
-webkit-border-image: none;
-webkit-box-align: center;
-webkit-box-shadow: rgb(255, 255, 255) 0px 1px 0px 0px inset;
-webkit-font-smoothing: antialiased;
-webkit-rtl-ordering: logical;
-webkit-user-select: text;
-webkit-writing-mode: horizontal-tb;
background-clip: padding-box;
background-color: rgb(238, 238, 238);
background-image: -webkit-linear-gradient(top, rgb(238, 238, 238), rgb(210, 210, 210));
border-bottom-color: rgb(202, 202, 202);
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
border-bottom-style: solid;
border-bottom-width: 1px;
border-left-color: rgb(202, 202, 202);
border-left-style: solid;
border-left-width: 1px;
border-right-color: rgb(202, 202, 202);
border-right-style: solid;
border-right-width: 1px;
border-top-color: rgb(202, 202, 202);
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-top-style: solid;
border-top-width: 1px;
box-shadow: rgb(255, 255, 255) 0px 1px 0px 0px inset;
box-sizing: border-box;
color: rgb(51, 51, 51);
cursor: pointer;
/* display: inline-block; */
display: inline;
float: left;
font-family: 'Open Sans', Verdana, Geneva, sans-serif;
font-size: 13px;
font-style: normal;
font-variant: normal;
font-weight: bold;
letter-spacing: normal;
line-height: 25.59375px;
margin-bottom: 15px;
margin: 0px;
padding: 0px;
text-align: center;
text-decoration: none;
text-indent: 0px;
text-shadow: rgb(248, 248, 248) 0px 1px 0px;
text-transform: none;
vertical-align: top;
white-space: pre-line;
width: 25px;
height: 25px;
word-spacing: 0px;
writing-mode: lr-tb;
}
.button.answered {
-webkit-box-shadow: rgb(97, 184, 225) 0px 1px 0px 0px inset;
background-color: rgb(29, 157, 217);
background-image: -webkit-linear-gradient(top, rgb(29, 157, 217), rgb(14, 124, 176));
border-bottom-color: rgb(13, 114, 162);
border-left-color: rgb(13, 114, 162);
border-right-color: rgb(13, 114, 162);
border-top-color: rgb(13, 114, 162);
box-shadow: rgb(97, 184, 225) 0px 1px 0px 0px inset;
color: rgb(255, 255, 255);
text-shadow: rgb(7, 103, 148) 0px 1px 0px;
}
.text {
display: inline;
float: left;
width: 80%;
text-align: left;
min-height: 30px;
margin-left: 20px;
height: auto;
margin-bottom: 20px;
cursor: pointer;
&.short {
width: 100px;
}
}
}
.stats {
min-height: 40px;
margin-top: 20px;
clear: both;
&.short {
margin-top: 0;
clear: none;
display: inline;
float: right;
width: 70%;
}
.bar {
width: 75%;
height: 20px;
border: 1px solid black;
display: inline;
float: left;
margin-right: 10px;
&.short {
width: 65%;
height: 20px;
margin-top: 3px;
}
.percent {
background-color: gray;
width: 0px;
height: 20px;
&.short { }
}
}
.number {
width: 80px;
display: inline;
float: right;
height: 28px;
text-align: right;
&.short {
width: 120px;
height: auto;
}
}
}
}
.poll_answer.answered {
-webkit-box-shadow: rgb(97, 184, 225) 0px 1px 0px 0px inset;
background-color: rgb(29, 157, 217);
background-image: -webkit-linear-gradient(top, rgb(29, 157, 217), rgb(14, 124, 176));
border-bottom-color: rgb(13, 114, 162);
border-left-color: rgb(13, 114, 162);
border-right-color: rgb(13, 114, 162);
border-top-color: rgb(13, 114, 162);
box-shadow: rgb(97, 184, 225) 0px 1px 0px 0px inset;
color: rgb(255, 255, 255);
text-shadow: rgb(7, 103, 148) 0px 1px 0px;
}
.graph_answer {
display: none;
clear: both;
width: 400px;
height: 400px;
margin-top: 30px;
margin-left: auto;
margin-right: auto;
}
}
class @Conditional class @Conditional
constructor: (element) -> constructor: (element, callerElId) ->
@el = $(element).find('.conditional-wrapper') @el = $(element).find('.conditional-wrapper')
@id = @el.data('problem-id')
@element_id = @el.attr('id')
@url = @el.data('url')
@render()
$: (selector) ->
$(selector, @el)
updateProgress: (response) => @callerElId = callerElId
if response.progress_changed
@el.attr progress: response.progress_status
@el.trigger('progressChanged')
render: (content) -> if @el.data('passed') is true
if content return
@el.html(content) else if @el.data('passed') is false
XModule.loadModules(@el) @passed = false
else else
@passed = null
if callerElId isnt undefined and @passed isnt null
dependencies = @el.data('depends')
if (typeof dependencies is 'string') and (dependencies.length > 0) and (dependencies.indexOf(callerElId) is -1)
return
@url = @el.data('url')
@render(element)
render: (element) ->
$.postWithPrefix "#{@url}/conditional_get", (response) => $.postWithPrefix "#{@url}/conditional_get", (response) =>
@el.html(response.html) if (((response.passed is true) && (@passed is false)) || (@passed is null))
XModule.loadModules(@el) @el.data 'passed', response.passed
@el.html ''
@el.append(i) for i in response.html
parentEl = $(element).parent()
parentId = parentEl.attr 'id'
if response.message is false
if parentId.indexOf('vert') is 0
parentEl.hide()
else
$(element).hide()
else
if parentId.indexOf('vert') is 0
parentEl.show()
else
$(element).show()
XModule.loadModules @el
class @PollModule
constructor: (element) ->
@el = element
@ajaxUrl = @$('.container').data('url')
@$('.upvote').on('click', () => $.postWithPrefix(@url('upvote'), @handleVote))
@$('.downvote').on('click', () => $.postWithPrefix(@url('downvote'), @handleVote))
$: (selector) -> $(selector, @el)
url: (target) -> "#{@ajaxUrl}/#{target}"
handleVote: (response) =>
@$('.container').replaceWith(response.results)
\ No newline at end of file
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
(function (requirejs, require, define) {
define('logme', [], function () {
var debugMode;
// debugMode can be one of the following:
//
// true - All messages passed to logme will be written to the internal
// browser console.
// false - Suppress all output to the internal browser console.
//
// Obviously, if anywhere there is a direct console.log() call, we can't do
// anything about it. That's why use logme() - it will allow to turn off
// the output of debug information with a single change to a variable.
debugMode = true;
return logme;
/*
* function: logme
*
* A helper function that provides logging facilities. We don't want
* to call console.log() directly, because sometimes it is not supported
* by the browser. Also when everything is routed through this function.
* the logging output can be easily turned off.
*
* logme() supports multiple parameters. Each parameter will be passed to
* console.log() function separately.
*
*/
function logme() {
var i;
if (
(typeof debugMode === 'undefined') ||
(debugMode !== true) ||
(typeof window.console === 'undefined')
) {
return;
}
for (i = 0; i < arguments.length; i++) {
window.console.log(arguments[i]);
}
} // End-of: function logme
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
window.Poll = function (el) {
RequireJS.require(['PollMain'], function (PollMain) {
new PollMain(el);
});
};
(function (requirejs, require, define) {
define('PollMain', ['logme'], function (logme) {
PollMain.prototype = {
'showAnswerGraph': function (poll_answers, total) {
var _this, totalValue;
totalValue = parseFloat(total);
if (isFinite(totalValue) === false) {
return;
}
_this = this;
$.each(poll_answers, function (index, value) {
var numValue, percentValue;
numValue = parseFloat(value);
if (isFinite(numValue) === false) {
return;
}
percentValue = (numValue / totalValue) * 100.0;
_this.answersObj[index].statsEl.show();
_this.answersObj[index].numberEl.html('' + value + ' (' + percentValue.toFixed(1) + '%)');
_this.answersObj[index].percentEl.css({
'width': '' + percentValue.toFixed(1) + '%'
});
});
},
'submitAnswer': function (answer, answerObj) {
var _this;
// Make sure that the user can answer a question only once.
if (this.questionAnswered === true) {
return;
}
this.questionAnswered = true;
_this = this;
answerObj.buttonEl.addClass('answered');
// Send the data to the server as an AJAX request. Attach a callback that will
// be fired on server's response.
$.postWithPrefix(
_this.ajax_url + '/' + answer, {},
function (response) {
_this.showAnswerGraph(response.poll_answers, response.total);
if (_this.wrapperSectionEl !== null) {
$(_this.wrapperSectionEl).find('.xmodule_ConditionalModule').each(function (index, value) {
new window.Conditional(value, _this.id.replace(/^poll_/, ''));
});
}
}
);
}, // End-of: 'submitAnswer': function (answer, answerEl) {
'postInit': function () {
var _this;
// Access this object inside inner functions.
_this = this;
if (
(this.jsonConfig.poll_answer.length > 0) &&
(this.jsonConfig.answers.hasOwnProperty(this.jsonConfig.poll_answer) === false)
) {
this.questionEl.append(
'<h3>Error!</h3>' +
'<p>XML data format changed. List of answers was modified, but poll data was not updated.</p>'
);
return;
}
// Get the DOM id of the question.
this.id = this.questionEl.attr('id');
// Get the URL to which we will post the users answer to the question.
this.ajax_url = this.questionEl.data('ajax-url');
this.questionHtmlMarkup = $('<div />').html(this.jsonConfig.question).text();
this.questionEl.append(this.questionHtmlMarkup);
// When the user selects and answer, we will set this flag to true.
this.questionAnswered = false;
this.answersObj = {};
this.shortVersion = true;
$.each(this.jsonConfig.answers, function (index, value) {
if (value.length >= 18) {
_this.shortVersion = false;
}
});
$.each(this.jsonConfig.answers, function (index, value) {
var answer;
answer = {};
_this.answersObj[index] = answer;
answer.el = $('<div class="poll_answer"></div>');
answer.questionEl = $('<div class="question"></div>');
answer.buttonEl = $('<div class="button"></div>');
answer.textEl = $('<div class="text"></div>');
answer.questionEl.append(answer.buttonEl);
answer.questionEl.append(answer.textEl);
answer.el.append(answer.questionEl);
answer.statsEl = $('<div class="stats"></div>');
answer.barEl = $('<div class="bar"></div>');
answer.percentEl = $('<div class="percent"></div>');
answer.barEl.append(answer.percentEl);
answer.numberEl = $('<div class="number"></div>');
answer.statsEl.append(answer.barEl);
answer.statsEl.append(answer.numberEl);
answer.statsEl.hide();
answer.el.append(answer.statsEl);
answer.textEl.html(value);
if (_this.shortVersion === true) {
$.each(answer, function (index, value) {
if (value instanceof jQuery) {
value.addClass('short');
}
});
}
answer.el.appendTo(_this.questionEl);
answer.textEl.on('click', function () {
_this.submitAnswer(index, answer);
});
answer.buttonEl.on('click', function () {
_this.submitAnswer(index, answer);
});
if (index === _this.jsonConfig.poll_answer) {
answer.buttonEl.addClass('answered');
_this.questionAnswered = true;
}
});
this.graphAnswerEl = $('<div class="graph_answer"></div>');
this.graphAnswerEl.hide();
this.graphAnswerEl.appendTo(this.questionEl);
// If it turns out that the user already answered the question, show the answers graph.
if (this.questionAnswered === true) {
this.showAnswerGraph(this.jsonConfig.poll_answers, this.jsonConfig.total);
}
} // End-of: 'postInit': function () {
}; // End-of: PollMain.prototype = {
return PollMain;
function PollMain(el) {
var _this;
this.questionEl = $(el).find('.poll_question');
if (this.questionEl.length !== 1) {
// We require one question DOM element.
logme('ERROR: PollMain constructor requires one question DOM element.');
return;
}
// Just a safety precussion. If we run this code more than once, multiple 'click' callback handlers will be
// attached to the same DOM elements. We don't want this to happen.
if (this.questionEl.attr('poll_main_processed') === 'true') {
logme(
'ERROR: PolMain JS constructor was called on a DOM element that has already been processed once.'
);
return;
}
// This element was not processed earlier.
// Make sure that next time we will not process this element a second time.
this.questionEl.attr('poll_main_processed', 'true');
// Access this object inside inner functions.
_this = this;
// DOM element which contains the current poll along with any conditionals. By default we assume that such
// element is not present. We will try to find it.
this.wrapperSectionEl = null;
(function (tempEl, c1) {
while (tempEl.tagName.toLowerCase() !== 'body') {
tempEl = $(tempEl).parent()[0];
c1 += 1;
if (
(tempEl.tagName.toLowerCase() === 'section') &&
($(tempEl).hasClass('xmodule_WrapperModule') === true)
) {
_this.wrapperSectionEl = tempEl;
break;
} else if (c1 > 50) {
// In case something breaks, and we enter an endless loop, a sane
// limit for loop iterations.
break;
}
}
}($(el)[0], 0));
try {
this.jsonConfig = JSON.parse(this.questionEl.children('.poll_question_div').html());
$.postWithPrefix(
'' + this.questionEl.data('ajax-url') + '/' + 'get_state', {},
function (response) {
_this.jsonConfig.poll_answer = response.poll_answer;
_this.jsonConfig.total = response.total;
$.each(response.poll_answers, function (index, value) {
_this.jsonConfig.poll_answers[index] = value;
});
_this.questionEl.children('.poll_question_div').html(JSON.stringify(_this.jsonConfig));
_this.postInit();
}
);
return;
} catch (err) {
logme(
'ERROR: Invalid JSON config for poll ID "' + this.id + '".',
'Error messsage: "' + err.message + '".'
);
return;
}
} // End-of: function PollMain(el) {
}); // End-of: define('PollMain', ['logme'], function (logme) {
// End-of: (function (requirejs, require, define) {
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
class @WrapperDescriptor extends XModule.Descriptor
constructor: (@element) ->
console.log 'WrapperDescriptor'
@$items = $(@element).find(".vert-mod")
@$items.sortable(
update: (event, ui) => @update()
)
save: ->
children: $('.vert-mod li', @element).map((idx, el) -> $(el).data('id')).toArray()
...@@ -74,7 +74,8 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): ...@@ -74,7 +74,8 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
# VS[compat]. Take this out once course conversion is done (perhaps leave the uniqueness check) # VS[compat]. Take this out once course conversion is done (perhaps leave the uniqueness check)
# tags that really need unique names--they store (or should store) state. # tags that really need unique names--they store (or should store) state.
need_uniq_names = ('problem', 'sequential', 'video', 'course', 'chapter', 'videosequence', 'timelimit') need_uniq_names = ('problem', 'sequential', 'video', 'course', 'chapter',
'videosequence', 'poll_question', 'timelimit')
attr = xml_data.attrib attr = xml_data.attrib
tag = xml_data.tag tag = xml_data.tag
......
"""Poll module is ungraded xmodule used by students to
to do set of polls.
On the client side we show:
If student does not yet anwered - Question with set of choices.
If student have answered - Question with statistics for each answers.
Student can't change his answer.
"""
import cgi
import json import json
import logging import logging
from copy import deepcopy
from collections import OrderedDict
from lxml import etree from lxml import etree
from pkg_resources import resource_string, resource_listdir from pkg_resources import resource_string
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.stringify import stringify_children
from xblock.core import Integer, Scope, Boolean from xmodule.mako_module import MakoModuleDescriptor
from xmodule.xml_module import XmlDescriptor
from xblock.core import Scope, String, Object, Boolean, List
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class PollModule(XModule): class PollModule(XModule):
"""Poll Module"""
js = {
'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee')],
'js': [resource_string(__name__, 'js/src/poll/logme.js'),
resource_string(__name__, 'js/src/poll/poll.js'),
resource_string(__name__, 'js/src/poll/poll_main.js')]
}
css = {'scss': [resource_string(__name__, 'css/poll/display.scss')]}
js_module_name = "Poll"
js = {'coffee': [resource_string(__name__, 'js/src/poll/display.coffee')]} # Name of poll to use in links to this poll
js_module_name = "PollModule" display_name = String(help="Display name for this module", scope=Scope.settings)
upvotes = Integer(help="Number of upvotes this poll has recieved", scope=Scope.content, default=0)
downvotes = Integer(help="Number of downvotes this poll has recieved", scope=Scope.content, default=0)
voted = Boolean(help="Whether this student has voted on the poll", scope=Scope.student_state, default=False) voted = Boolean(help="Whether this student has voted on the poll", scope=Scope.student_state, default=False)
poll_answer = String(help="Student answer", scope=Scope.student_state, default='')
poll_answers = Object(help="All possible answers for the poll fro other students", scope=Scope.content)
answers = List(help="Poll answers from xml", scope=Scope.content, default=[])
question = String(help="Poll question", scope=Scope.content, default='')
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
''' """Ajax handler.
Handle ajax calls to this video.
TODO (vshnayder): This is not being called right now, so the position Args:
is not being saved. dispatch: string request slug
''' get: dict request get parameters
if self.voted:
return json.dumps({'error': 'Already Voted!'})
elif dispatch == 'upvote':
self.upvotes += 1
self.voted = True
return json.dumps({'results': self.get_html()})
elif dispatch == 'downvote':
self.downvotes += 1
self.voted = True
return json.dumps({'results': self.get_html()})
return json.dumps({'error': 'Unknown Command!'}) Returns:
json string
"""
if dispatch in self.poll_answers and not self.voted:
# FIXME: fix this, when xblock will support mutable types.
# Now we use this hack.
temp_poll_answers = self.poll_answers
temp_poll_answers[dispatch] += 1
self.poll_answers = temp_poll_answers
self.voted = True
self.poll_answer = dispatch
return json.dumps({'poll_answers': self.poll_answers,
'total': sum(self.poll_answers.values()),
'callback': {'objectName': 'Conditional'}
})
elif dispatch == 'get_state':
return json.dumps({'poll_answer': self.poll_answer,
'poll_answers': self.poll_answers,
'total': sum(self.poll_answers.values())
})
else: # return error message
return json.dumps({'error': 'Unknown Command!'})
def get_html(self): def get_html(self):
return self.system.render_template('poll.html', { """Renders parameters to template."""
'upvotes': self.upvotes, params = {
'downvotes': self.downvotes, 'element_id': self.location.html_id(),
'voted': self.voted, 'element_class': self.location.category,
'ajax_url': self.system.ajax_url, 'ajax_url': self.system.ajax_url,
}) 'configuration_json': self.dump_poll(),
}
self.content = self.system.render_template('poll.html', params)
return self.content
def dump_poll(self):
"""Dump poll information.
Returns:
string - Serialize json.
"""
# FIXME: hack for resolving caching `default={}` during definition
# poll_answers field
if self.poll_answers is None:
self.poll_answers = {}
answers_to_json = OrderedDict()
# FIXME: fix this, when xblock support mutable types.
# Now we use this hack.
temp_poll_answers = self.poll_answers
# Fill self.poll_answers, prepare data for template context.
for answer in self.answers:
# Set default count for answer = 0.
if answer['id'] not in temp_poll_answers:
temp_poll_answers[answer['id']] = 0
answers_to_json[answer['id']] = cgi.escape(answer['text'])
self.poll_answers = temp_poll_answers
return json.dumps({'answers': answers_to_json,
'question': cgi.escape(self.question),
# to show answered poll after reload:
'poll_answer': self.poll_answer,
'poll_answers': self.poll_answers if self.voted else {},
'total': sum(self.poll_answers.values()) if self.voted else 0})
class PollDescriptor(MakoModuleDescriptor, XmlDescriptor):
_tag_name = 'poll_question'
_child_tag_name = 'answer'
class PollDescriptor(RawDescriptor):
module_class = PollModule module_class = PollModule
template_dir_name = 'poll'
stores_state = True stores_state = True
template_dir_name = "poll"
\ No newline at end of file answers = List(help="Poll answers", scope=Scope.content, default=[])
question = String(help="Poll question", scope=Scope.content, default='')
display_name = String(help="Display name for this module", scope=Scope.settings)
id = String(help="ID attribute for this module", scope=Scope.settings)
@classmethod
def definition_from_xml(cls, xml_object, system):
"""Pull out the data into dictionary.
Args:
xml_object: xml from file.
system: `system` object.
Returns:
(definition, children) - tuple
definition - dict:
{
'answers': <List of answers>,
'question': <Question string>
}
"""
# Check for presense of required tags in xml.
if len(xml_object.xpath(cls._child_tag_name)) == 0:
raise ValueError("Poll_question definition must include \
at least one 'answer' tag")
xml_object_copy = deepcopy(xml_object)
answers = []
for element_answer in xml_object_copy.findall(cls._child_tag_name):
answer_id = element_answer.get('id', None)
if answer_id:
answers.append({
'id': answer_id,
'text': stringify_children(element_answer)
})
xml_object_copy.remove(element_answer)
definition = {
'answers': answers,
'question': stringify_children(xml_object_copy)
}
children = []
return (definition, children)
def definition_to_xml(self, resource_fs):
"""Return an xml element representing to this definition."""
poll_str = '<{tag_name}>{text}</{tag_name}>'.format(
tag_name=self._tag_name, text=self.question)
xml_object = etree.fromstring(poll_str)
xml_object.set('display_name', self.display_name)
xml_object.set('id', self.id)
def add_child(xml_obj, answer):
child_str = '<{tag_name} id="{id}">{text}</{tag_name}>'.format(
tag_name=self._child_tag_name, id=answer['id'],
text=answer['text'])
child_node = etree.fromstring(child_str)
xml_object.append(child_node)
for answer in self.answers:
add_child(xml_object, answer)
return xml_object
from itertools import chain # -*- coding: utf-8 -*-
from lxml import etree from lxml import etree
......
...@@ -28,7 +28,6 @@ def strip_filenames(descriptor): ...@@ -28,7 +28,6 @@ def strip_filenames(descriptor):
strip_filenames(d) strip_filenames(d)
class RoundTripTestCase(unittest.TestCase): class RoundTripTestCase(unittest.TestCase):
''' Check that our test courses roundtrip properly. ''' Check that our test courses roundtrip properly.
Same course imported , than exported, then imported again. Same course imported , than exported, then imported again.
...@@ -91,7 +90,6 @@ class RoundTripTestCase(unittest.TestCase): ...@@ -91,7 +90,6 @@ class RoundTripTestCase(unittest.TestCase):
self.assertEquals(initial_import.modules[course_id][location], self.assertEquals(initial_import.modules[course_id][location],
second_import.modules[course_id][location]) second_import.modules[course_id][location])
def setUp(self): def setUp(self):
self.maxDiff = None self.maxDiff = None
...@@ -104,6 +102,9 @@ class RoundTripTestCase(unittest.TestCase): ...@@ -104,6 +102,9 @@ class RoundTripTestCase(unittest.TestCase):
def test_full_roundtrip(self): def test_full_roundtrip(self):
self.check_export_roundtrip(DATA_DIR, "full") self.check_export_roundtrip(DATA_DIR, "full")
def test_conditional_and_poll_roundtrip(self):
self.check_export_roundtrip(DATA_DIR, "conditional_and_poll")
def test_selfassessment_roundtrip(self): def test_selfassessment_roundtrip(self):
#Test selfassessment xmodule to see if it exports correctly #Test selfassessment xmodule to see if it exports correctly
self.check_export_roundtrip(DATA_DIR, "self_assessment") self.check_export_roundtrip(DATA_DIR, "self_assessment")
......
# -*- coding: utf-8 -*-
from path import path from path import path
import unittest import unittest
from fs.memoryfs import MemoryFS from fs.memoryfs import MemoryFS
...@@ -76,7 +78,6 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -76,7 +78,6 @@ class ImportTestCase(BaseCourseTestCase):
self.assertEqual(descriptor.__class__.__name__, self.assertEqual(descriptor.__class__.__name__,
'ErrorDescriptor') 'ErrorDescriptor')
def test_unique_url_names(self): def test_unique_url_names(self):
'''Check that each error gets its very own url_name''' '''Check that each error gets its very own url_name'''
bad_xml = '''<sequential display_name="oops"><video url="hi"></sequential>''' bad_xml = '''<sequential display_name="oops"><video url="hi"></sequential>'''
...@@ -88,7 +89,6 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -88,7 +89,6 @@ class ImportTestCase(BaseCourseTestCase):
self.assertNotEqual(descriptor1.location, descriptor2.location) self.assertNotEqual(descriptor1.location, descriptor2.location)
def test_reimport(self): def test_reimport(self):
'''Make sure an already-exported error xml tag loads properly''' '''Make sure an already-exported error xml tag loads properly'''
...@@ -230,7 +230,6 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -230,7 +230,6 @@ class ImportTestCase(BaseCourseTestCase):
check_for_key('graceperiod', course) check_for_key('graceperiod', course)
def test_policy_loading(self): def test_policy_loading(self):
"""Make sure that when two courses share content with the same """Make sure that when two courses share content with the same
org and course names, policy applies to the right one.""" org and course names, policy applies to the right one."""
...@@ -254,7 +253,6 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -254,7 +253,6 @@ class ImportTestCase(BaseCourseTestCase):
# appropriate attribute maps -- 'graded' should be True, not 'true' # appropriate attribute maps -- 'graded' should be True, not 'true'
self.assertEqual(toy.lms.graded, True) self.assertEqual(toy.lms.graded, True)
def test_definition_loading(self): def test_definition_loading(self):
"""When two courses share the same org and course name and """When two courses share the same org and course name and
both have a module with the same url_name, the definitions shouldn't clash. both have a module with the same url_name, the definitions shouldn't clash.
...@@ -274,7 +272,6 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -274,7 +272,6 @@ class ImportTestCase(BaseCourseTestCase):
self.assertEqual(etree.fromstring(toy_video.data).get('youtube'), "1.0:p2Q6BrNhdh8") self.assertEqual(etree.fromstring(toy_video.data).get('youtube'), "1.0:p2Q6BrNhdh8")
self.assertEqual(etree.fromstring(two_toy_video.data).get('youtube'), "1.0:p2Q6BrNhdh9") self.assertEqual(etree.fromstring(two_toy_video.data).get('youtube'), "1.0:p2Q6BrNhdh9")
def test_colon_in_url_name(self): def test_colon_in_url_name(self):
"""Ensure that colons in url_names convert to file paths properly""" """Ensure that colons in url_names convert to file paths properly"""
...@@ -331,6 +328,22 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -331,6 +328,22 @@ class ImportTestCase(BaseCourseTestCase):
self.assertEqual(len(video.url_name), len('video_') + 12) self.assertEqual(len(video.url_name), len('video_') + 12)
def test_poll_xmodule(self):
modulestore = XMLModuleStore(DATA_DIR, course_dirs=['conditional_and_poll'])
course = modulestore.get_courses()[0]
chapters = course.get_children()
ch1 = chapters[0]
sections = ch1.get_children()
self.assertEqual(len(sections), 1)
location = course.location
location = Location(location.tag, location.org, location.course,
'sequential', 'Problem_Demos')
module = modulestore.get_instance(course.id, location)
self.assertEqual(len(module.children), 2)
def test_error_on_import(self): def test_error_on_import(self):
'''Check that when load_error_module is false, an exception is raised, rather than returning an ErrorModule''' '''Check that when load_error_module is false, an exception is raised, rather than returning an ErrorModule'''
......
# -*- coding: utf-8 -*-
import json
import unittest
from xmodule.poll_module import PollModule
from xmodule.conditional_module import ConditionalModule
class LogicTest(unittest.TestCase):
"""Base class for testing xmodule logic."""
xmodule_class = None
raw_model_data = {}
def setUp(self):
self.system = None
self.location = None
self.descriptor = None
self.xmodule = self.xmodule_class(self.system, self.location,
self.descriptor, self.raw_model_data)
def ajax_request(self, dispatch, get):
return json.loads(self.xmodule.handle_ajax(dispatch, get))
class PollModuleTest(LogicTest):
xmodule_class = PollModule
raw_model_data = {
'poll_answers': {'Yes': 1, 'Dont_know': 0, 'No': 0},
'voted': False,
'poll_answer': ''
}
def test_bad_ajax_request(self):
response = self.ajax_request('bad_answer', {})
self.assertDictEqual(response, {'error': 'Unknown Command!'})
def test_good_ajax_request(self):
response = self.ajax_request('No', {})
poll_answers = response['poll_answers']
total = response['total']
callback = response['callback']
self.assertDictEqual(poll_answers, {'Yes': 1, 'Dont_know': 0, 'No': 1})
self.assertEqual(total, 2)
self.assertDictEqual(callback, {'objectName': 'Conditional'})
self.assertEqual(self.xmodule.poll_answer, 'No')
class ConditionalModuleTest(LogicTest):
xmodule_class = ConditionalModule
raw_model_data = {
'contents': 'Some content'
}
def test_ajax_request(self):
# Mock is_condition_satisfied
self.xmodule.is_condition_satisfied = lambda: True
response = self.ajax_request('No', {})
html = response['html']
self.assertEqual(html, 'Some content')
# Same as vertical,
# But w/o css delimiters between children
from xmodule.vertical_module import VerticalModule, VerticalDescriptor
from pkg_resources import resource_string
# HACK: This shouldn't be hard-coded to two types
# OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem']
class WrapperModule(VerticalModule):
''' Layout module for laying out submodules vertically w/o css delimiters'''
has_children = True
css = {'scss': [resource_string(__name__, 'css/wrapper/display.scss')]}
class WrapperDescriptor(VerticalDescriptor):
module_class = WrapperModule
has_children = True
js = {'coffee': [resource_string(__name__, 'js/src/vertical/edit.coffee')]}
js_module_name = "VerticalDescriptor"
Any place that says "YEAR_SEMESTER" needs to be replaced with something
in the form "2013_Spring". Take note of this name exactly, you'll need to
use it everywhere, precisely - capitalization is very important.
See https://github.com/MITx/mitx/blob/master/doc/xml-format.md for more on all this.
-----------------------
about/: Files that live here will be visible OUTSIDE OF COURSEWARE.
YEAR_SEMESTER/
end_date.html: Specifies in plain-text the end date of the course
overview.html: Text of the overview of the course
short_description.html: 10-15 words about the course
prerequisites.html: Any prerequisites for the course, or None if there are none.
course/
YEAR_SEMESTER.xml: This is your top-level xml page that points at chapters.
Can just be <course/> for now.
course.xml: This top level file points at a file in roots/. See creating_course.xml.
creating_course.xml: Explains how to create course.xml
info/: Files that live here will be visible on the COURSE LANDING PAGE
(Course Info) WITHIN THE COURSEWARE.
YEAR_SEMESTER/
handouts.html: A list of handouts, or an empty file if there are none
(if this file doesn't exist, it displays an error)
updates.html: Course updates.
policies/
YEAR_SEMESTER/
policy.json: See https://github.com/MITx/mitx/blob/master/doc/xml-format.md
for more on the fields specified by this file.
grading_policy.json: Optional -- you don't need it to get a course off the
ground but will eventually. For more info see
https://github.com/MITx/mitx/blob/master/doc/course_grading.md
roots/
YEAR_SEMESTER.xml: Looks something like
<course url_name="YEAR_SEMESTER" org="ORG" course="COURSENUM"/>
where ORG in {"MITx", "HarvardX", "BerkeleyX"}
static/
See README.
images/
course_image.jpg: You MUST have an image named this to be the background
banner image on edx.org
-----------------------
\ No newline at end of file
content-harvard-justicex
========================
\ No newline at end of file
<section class="about">
<h2>About ER22x</h2>
<p>Justice is a critical analysis of classical and contemporary theories of justice, including discussion of present-day applications. Topics include affirmative action, income distribution, same-sex marriage, the role of markets, debates about rights (human rights and property rights), arguments for and against equality, dilemmas of loyalty in public and private life. The course invites students to subject their own views on these controversies to critical examination.</p>
<p>The principle readings for the course are texts by Aristotle, John Locke, Immanuel Kant, John Stuart Mill, and John Rawls. Other assigned readings include writings by contemporary philosophers, court cases, and articles about political controversies that raise philosophical questions.</p>
<!--
<p>The assigned readings will be freely available online. They are also collected in an edited volume, <emph>Justice: A Reader</emph> (ed. Michael Sandel, Oxford University Press). Students who would like further guidance on the themes of the lectures can read Michael Sandel, <emph>Justice: What’s the Right Thing to Do?</emph> (Recommended but not required.)
</p>
-->
</section>
<section class="course-staff">
<h2>Course instructor</h3>
<article class="teacher">
<!-- TODO: Need to change image location -->
<!-- First Professor -->
<div class="teacher-image"><img src="/static/images/professor-sandel.jpg"/></div>
<h3>Michael J. Sandel</h3>
<p>Michael J. Sandel is the Anne T. and Robert M. Bass Professor of Government at Harvard University, where he teaches political philosophy.  His course "Justice" has enrolled more than 15,000 Harvard students.  Sandel's writings have been published in 21 languages.  His books include <i>What Money Can't Buy: The Moral Limits of Markets</i> (2012); <i>Justice: What's the Right Thing to Do?</i> (2009); <i>The Case against Perfection: Ethics in the Age of Genetic Engineering</i> (2007); <i>Public Philosophy: Essays on Morality in Politics</i> (2005); <i>Democracy's Discontent</i> (1996); and <i>Liberalism and the Limits of Justice</i>(1982; 2nd ed., 1998). </p>
<p><br></p>
</section>
<section class="faq">
<section class="responses">
<h2>Frequently Asked Questions</h2>
<article class="response">
<h3>How much does it cost to take the course?</h3>
<p>Nothing! The course is free.</p>
</article>
<article class="response">
<h3>Does the course have any prerequisites?</h3>
<p>No. Only an interest in thinking through some of the big ethical and civic questions we face in our everyday lives.</p>
</article>
<article class="response">
<h3>Do I need any other materials to take the course?</h3>
<p>No. As long as you’ve got a computer to access the website, you are ready to take the course.</p>
</article>
<article class="response">
<h3>Is there a textbook for the course?</h3>
<p>All of the course readings that are in the public domain are freely available online, at links provided on the course website. The course can be taken using these free resources alone. For those who wish to purchase a printed version of the assigned readings, an edited volume entitled, Justice: A Reader (ed., Michael Sandel) is available in paperback from Oxford University Press (in bookstores and from online booksellers). Those who would like supplementary readings on the themes of the lectures can find them in Michael Sandel's book Justice: What's the Right Thing to Do?, which is available in various languages throughout the world. This book is not required, and the course can be taken using the free online resources alone.</p>
</article>
<article class="response">
<h3>Do I need to watch the lectures at a specific time?</h3>
<p>No. You can watch the lectures at your leisure.</p>
</article>
<article class="response">
<h3>Will I be able to participate in class discussions?</h3>
<p>Yes, in several ways: </p>
<ol>
<li><p> Each lecture invites you to respond to a poll question related to the themes of the lecture. If you respond to the question, you will be presented with a challenge to the opinion you have expressed, and invited to reply to the challenge. You can also, if you wish, comment on the opinions and responses posted by other students in the course, continuing the discussion.</p></li>
<li><p> In addition to the poll question, each class contains a discussion prompt that invites you to offer your view on a controversial question related to the lecture. If you wish, you can respond to this question, and then see what other students have to say about the argument you present. You can also comment on the opinions posted by other students. One aim of the course is to promote reasoned public dialogue about hard moral and political questions. </p></li>
<li><p> Each week, there will be an optional live dialogue enabling students to interact with instructors and participants from around the world.</p></li>
</ol>
</article>
<article class="response">
<h3>Will certificates be awarded?</h3>
<p>Yes. Online learners who achieve a passing grade in a course can earn a certificate of mastery. These certificates will indicate you have successfully completed the course, but will not include a specific grade. Certificates will be issued by edX under the name of HarvardX, designating the institution from which the course originated. </p>
</article>
</section>
</section>
\ No newline at end of file
JusticeX is an introduction to moral and political philosophy, including discussion of contemporary dilemmas and controversies.
\ No newline at end of file
<iframe width="560" height="315" src="http://www.youtube.com/embed/fajlZMdPkKE#!" frameborder="0" allowfullscreen></iframe>
\ No newline at end of file
<chapter>
<sequential url_name="Problem_Demos"/>
</chapter>
roots/2013_Spring.xml
\ No newline at end of file
<!-- Name this file eg "2012_Fall.xml" or "2013_Spring.xml"
Take note of this name exactly, you'll need to use it everywhere. -->
<course>
<chapter url_name="Staff"/>
</course>
<!-- A file named "course.xml" in your top-level should point
at the appropriate roots file. You can do so like this:
$ rm course.xml
$ ln -s roots/YEAR_SEMESTER.xml course.xml
Ask Sarina for help with this. -->
<ol>
<li>A list of course handouts, or an empty file if there are none.</li>
</ol>
<!-- If you wish to make a welcome announcement -->
<ol>
<li><h2>December 9</h2>
<section class="update-description">
<p>Announcement text</p>
</section>
</li>
</ol>
{
"course/2013_Spring": {
"start": "2099-01-01T00:00",
"advertised_start" : "Spring 2013",
"display_name": "Justice"
}
}
<course url_name="2013_Spring" org="HarvardX" course="ER22x"/>
<sequential>
<vertical>
<poll_question id="first_poll" display_name="first poll">
<h3>What's the Right Thing to Do?</h3>
<p>Suppose four shipwrecked sailors are stranded at sea in a lifeboat, without
food or water. Would it be wrong for three of them to kill and eat the cabin
boy, in order to save their own lives?</p>
<answer id="Yes">Yes</answer>
<answer id="No">No</answer>
<answer id="Dont_know">Don't know</answer>
</poll_question>
<poll_question id="second_poll" display_name="second poll">
<h3>What's the Right Thing to Do?</h3>
<p>Suppose four shipwrecked sailors are stranded at sea in a lifeboat, without
food or water. Would it be wrong for three of them to kill and eat the cabin
boy, in order to save their own lives?</p>
<answer id="Yes">Yes</answer>
<answer id="No">No</answer>
<answer id="Dont_know">Don't know</answer>
</poll_question>
</vertical>
<wrapper>
<!-- Test many show tags -->
<html>Condition: first_poll - Yes</html>
<conditional sources="i4x://HarvardX/ER22x/poll_question/first_poll" poll_answer="Yes">
<html>In first condition.</html>
<show sources="i4x://HarvardX/ER22x/poll_question/second_poll"/>
</conditional>
</wrapper>
</sequential>
Images, handouts, and other statically-served content should go ONLY
in this directory.
Images for the front page should go in static/images. The frontpage
banner MUST be named course_image.jpg
\ No newline at end of file
...@@ -75,7 +75,7 @@ class ModelDataCache(object): ...@@ -75,7 +75,7 @@ class ModelDataCache(object):
if depth is None or depth > 0: if depth is None or depth > 0:
new_depth = depth - 1 if depth is not None else depth new_depth = depth - 1 if depth is not None else depth
for child in descriptor.get_children(): for child in descriptor.get_children() + descriptor.get_required_module_descriptors():
descriptors.extend(get_child_descriptors(child, new_depth, descriptor_filter)) descriptors.extend(get_child_descriptors(child, new_depth, descriptor_filter))
return descriptors return descriptors
......
...@@ -35,7 +35,7 @@ SESSION_ENGINE = 'django.contrib.sessions.backends.cache' ...@@ -35,7 +35,7 @@ SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
# Enable Berkeley forums # Enable Berkeley forums
MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = False
# IMPORTANT: With this enabled, the server must always be behind a proxy that # IMPORTANT: With this enabled, the server must always be behind a proxy that
# strips the header HTTP_X_FORWARDED_PROTO from client requests. Otherwise, # strips the header HTTP_X_FORWARDED_PROTO from client requests. Otherwise,
......
...@@ -119,6 +119,10 @@ div.course-wrapper { ...@@ -119,6 +119,10 @@ div.course-wrapper {
} }
} }
section.xmodule_WrapperModule ol.vert-mod > li {
border-bottom: none;
}
section.tutorials { section.tutorials {
h2 { h2 {
margin-bottom: lh(); margin-bottom: lh();
...@@ -219,7 +223,7 @@ div.course-wrapper { ...@@ -219,7 +223,7 @@ div.course-wrapper {
.xmodule_VideoModule { .xmodule_VideoModule {
margin-bottom: 30px; margin-bottom: 30px;
} }
textarea.short-form-response { textarea.short-form-response {
...@@ -237,7 +241,7 @@ section.self-assessment { ...@@ -237,7 +241,7 @@ section.self-assessment {
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
} }
div { div {
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
......
<div id="conditional_${element_id}" class="conditional-wrapper" data-problem-id="${id}" data-url="${ajax_url}"></div> <div
id="conditional_${element_id}"
class="conditional-wrapper"
data-problem-id="${id}"
data-url="${ajax_url}"
data-depends="${depends}"
>
</div>
<% <%
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
reqm = module.required_modules[0]
course_id = module.system.course_id # course_id = module.location.course_id
condition = module.condition def get_course_id(module):
%> return module.location.org +'/' + module.location.course +'/' + \
module.system.ajax_url.split('/')[4]
<p><a href="${reverse('jump_to',kwargs=dict(course_id=course_id, location=reqm.location.url()))}">${reqm.display_name}</a> def _message(reqm, message):
must be return message.format(link="<a href={url}>{url_name}</a>".format(
% if 'attempted' in condition: url = reverse('jump_to', kwargs=dict(course_id=get_course_id(reqm),
attempted location=reqm.location.url())),
% else: url_name = reqm.display_name))
completed %>
% if message:
% for reqm in module.required_modules:
<p>${_message(reqm, message)}</p>
% endfor
% endif % endif
before this will become visible.</p>
<div class='container' data-url="${ajax_url}"> <section
% if voted: id="poll_${element_id}"
<div>Upvotes: ${upvotes}</div> class="${element_class}"
<div>Downvotes: ${downvotes}</div> data-ajax-url="${ajax_url}"
% else: >
<a class="upvote">Yes</a> <!-- Hidden field to read configuration JSON from. -->
<a class="downvote">No</a> <div class="${element_class}_div" id="${element_id}_json" style="display: none;">${configuration_json}</div>
% endif </section>
</div> \ No newline at end of file
\ No newline at end of file
...@@ -183,6 +183,12 @@ end ...@@ -183,6 +183,12 @@ end
TEST_TASK_DIRS = [] TEST_TASK_DIRS = []
task :fastlms do
# this is >2 times faster that rake [lms], and does not need web, good for local dev
django_admin = ENV['DJANGO_ADMIN_PATH'] || select_executable('django-admin.py', 'django-admin')
sh("#{django_admin} runserver --traceback --settings=lms.envs.dev --pythonpath=.")
end
[:lms, :cms].each do |system| [:lms, :cms].each do |system|
report_dir = report_dir_path(system) report_dir = report_dir_path(system)
......
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