diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 490f49a41c..d701db33a3 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -53,7 +53,7 @@ def index(request): """ courses = modulestore().get_items(['i4x', None, None, 'course', None]) return render_to_response('index.html', { - 'courses': [(course.metadata['display_name'], + 'courses': [(course.metadata.get('display_name'), reverse('course_index', args=[ course.location.org, course.location.course, diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 410f74ee07..90a9629351 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -14,9 +14,11 @@ $yellow: #fff8af; $cream: #F6EFD4; $border-color: #ddd; + // edX colors $blue: rgb(29,157,217); $pink: rgb(182,37,104); +$error-red: rgb(253, 87, 87); @mixin hide-text { background-color: transparent; diff --git a/cms/static/sass/_calendar.scss b/cms/static/sass/_calendar.scss index 35609b2d56..4c007bb561 100644 --- a/cms/static/sass/_calendar.scss +++ b/cms/static/sass/_calendar.scss @@ -330,11 +330,6 @@ section.cal { &:hover { opacity: 1; - width: flex-grid(5) + flex-gutter(); - - + section.main-content { - width: flex-grid(7); - } } > header { diff --git a/common/djangoapps/external_auth/admin.py b/common/djangoapps/external_auth/admin.py index 343492bca7..e93325bcb2 100644 --- a/common/djangoapps/external_auth/admin.py +++ b/common/djangoapps/external_auth/admin.py @@ -5,4 +5,8 @@ django admin pages for courseware model from external_auth.models import * from django.contrib import admin -admin.site.register(ExternalAuthMap) +class ExternalAuthMapAdmin(admin.ModelAdmin): + search_fields = ['external_id','user__username'] + date_hierarchy = 'dtcreated' + +admin.site.register(ExternalAuthMap, ExternalAuthMapAdmin) diff --git a/common/djangoapps/track/admin.py b/common/djangoapps/track/admin.py new file mode 100644 index 0000000000..1f19c59a93 --- /dev/null +++ b/common/djangoapps/track/admin.py @@ -0,0 +1,8 @@ +''' +django admin pages for courseware model +''' + +from track.models import * +from django.contrib import admin + +admin.site.register(TrackingLog) diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py index 066d83ed3e..80514cf8d4 100644 --- a/common/djangoapps/xmodule_modifiers.py +++ b/common/djangoapps/xmodule_modifiers.py @@ -125,7 +125,7 @@ def add_histogram(get_html, module, user): mstart = getattr(module.descriptor,'start') if mstart is not None: is_released = "Yes!" if (now > mstart) else "Not yet" - + staff_context = {'definition': module.definition.get('data'), 'metadata': json.dumps(module.metadata, indent=4), 'location': module.location, @@ -133,6 +133,7 @@ def add_histogram(get_html, module, user): 'source_file' : source_file, 'source_url': '%s/%s/tree/master/%s' % (giturl,data_dir,source_file), 'category': str(module.__class__.__name__), + # Template uses element_id in js function names, so can't allow dashes 'element_id': module.location.html_id().replace('-','_'), 'edit_link': edit_link, 'user': user, diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index f386c9fe24..626ad48c36 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -265,7 +265,7 @@ class LoncapaProblem(object): # include solutions from ... stanzas for entry in self.tree.xpath("//" + "|//".join(solution_types)): answer = etree.tostring(entry) - if answer: answer_map[entry.get('id')] = answer + if answer: answer_map[entry.get('id')] = contextualize_text(answer, self.context) log.debug('answer_map = %s' % answer_map) return answer_map @@ -382,10 +382,10 @@ class LoncapaProblem(object): original_path = sys.path for script in scripts: - sys.path = original_path + self._extract_system_path(script) stype = script.get('type') + if stype: if 'javascript' in stype: continue # skip javascript diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index d9b864c5bc..187d2fd422 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -326,8 +326,16 @@ def textline_dynamath(element, value, status, render_template, msg=''): count = int(eid.split('_')[-2]) - 1 # HACK size = element.get('size') hidden = element.get('hidden', '') # if specified, then textline is hidden and id is stored in div of name given by hidden + + # Preprocessor to insert between raw input and Mathjax + preprocessor = {'class_name': element.get('preprocessorClassName',''), + 'script_src': element.get('preprocessorSrc','')} + if '' in preprocessor.values(): + preprocessor = None + context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size, 'msg': msg, 'hidden': hidden, + 'preprocessor': preprocessor, } html = render_template("textinput_dynamath.html", context) return etree.XML(html) diff --git a/common/lib/capa/capa/javascript_problem_generator.js b/common/lib/capa/capa/javascript_problem_generator.js index 8c8d39b19f..1cd4616c5a 100644 --- a/common/lib/capa/capa/javascript_problem_generator.js +++ b/common/lib/capa/capa/javascript_problem_generator.js @@ -11,13 +11,11 @@ importAll("xproblem"); generatorModulePath = process.argv[2]; dependencies = JSON.parse(process.argv[3]); -seed = process.argv[4]; +seed = JSON.parse(process.argv[4]); params = JSON.parse(process.argv[5]); if(seed==null){ seed = 4; -}else{ - seed = parseInt(seed); } for(var i = 0; i < dependencies.length; i++){ diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index b803452b8c..5b89b78867 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -408,7 +408,7 @@ class JavascriptResponse(LoncapaResponse): output = self.call_node([generator_file, self.generator, json.dumps(self.generator_dependencies), - json.dumps(str(self.system.seed)), + json.dumps(str(self.context['the_lcp'].seed)), json.dumps(self.params)]).strip() return json.loads(output) @@ -971,8 +971,9 @@ def sympy_check2(): # build map giving "correct"ness of the answer(s) correct_map = CorrectMap() for k in range(len(idset)): + npoints = self.maxpoints[idset[k]] if correct[k] == 'correct' else 0 correct_map.set(idset[k], correct[k], msg=messages[k], - npoints=self.maxpoints[idset[k]]) + npoints=npoints) return correct_map def get_answers(self): diff --git a/common/lib/capa/capa/templates/textinput_dynamath.html b/common/lib/capa/capa/templates/textinput_dynamath.html index 645153fd92..d1de22ef27 100644 --- a/common/lib/capa/capa/templates/textinput_dynamath.html +++ b/common/lib/capa/capa/templates/textinput_dynamath.html @@ -1,7 +1,13 @@ ### -### version of textline.html which does dynammic math +### version of textline.html which does dynamic math ### -
+
+ + % if preprocessor is not None: +
+
+ % endif + % if state == 'unsubmitted':
% elif state == 'correct': @@ -15,27 +21,26 @@
% endif - + +

+ % if state == 'unsubmitted': + unanswered + % elif state == 'correct': + correct + % elif state == 'incorrect': + incorrect + % elif state == 'incomplete': + incomplete + % endif +

-

- % if state == 'unsubmitted': - unanswered - % elif state == 'correct': - correct - % elif state == 'incorrect': - incorrect - % elif state == 'incomplete': - incomplete - % endif -

+

-

- -
`{::}`
+
`{::}`
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index d186bcc39c..8bf1a56404 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -507,8 +507,12 @@ class CapaModule(XModule): # 'success' will always be incorrect event_info['correct_map'] = correct_map.get_dict() event_info['success'] = success + event_info['attempts'] = self.attempts self.system.track_function('save_problem_check', event_info) + if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback + self.system.psychometrics_handler(self.get_instance_state()) + # render problem into HTML html = self.get_problem_html(encapsulate=False) diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index b6bce5f83d..0591a01843 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -158,7 +158,7 @@ section.problem { border: 1px solid #e3e3e3; @include inline-block; @include border-radius(4px); - min-width: 300px; + min-width: 30px; } } } @@ -311,6 +311,10 @@ section.problem { text-align: left; } + td { + text-align: left; + } + caption, th, td { padding: .25em .75em .25em 0; padding: .25rem .75rem .25rem 0; @@ -432,7 +436,7 @@ section.problem { .detailed-solution { border: 1px solid #ddd; - padding: 9px 9px 20px; + padding: 9px 15px 20px; margin-bottom: 10px; background: #FFF; position: relative; diff --git a/common/lib/xmodule/xmodule/css/sequence/display.scss b/common/lib/xmodule/xmodule/css/sequence/display.scss index 25d2c26dda..94d6a201c7 100644 --- a/common/lib/xmodule/xmodule/css/sequence/display.scss +++ b/common/lib/xmodule/xmodule/css/sequence/display.scss @@ -32,7 +32,7 @@ nav.sequence-nav { .sequence-list-wrapper { position: relative; - z-index: 9999; + z-index: 99; border: 1px solid #ccc; height: 44px; margin: 0 30px; @@ -297,7 +297,6 @@ nav.sequence-bottom { ul { @extend .clearfix; @include inline-block(); - width: 103px; li { float: left; diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index 504d5df8cf..e8aba4d671 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -157,6 +157,7 @@ div.video { opacity: 1; padding: 0; margin: 0; + list-style: none; } } @@ -411,6 +412,7 @@ div.video { width: flex-grid(3, 9); margin: 0; font-size: 14px; + list-style: none; li { border: 0; diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index e27b46d04e..7376418dff 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -11,7 +11,9 @@ class @Problem $(selector, @el) bind: => - MathJax.Hub.Queue ["Typeset", MathJax.Hub] + @el.find('.problem > div').each (index, element) => + MathJax.Hub.Queue ["Typeset", MathJax.Hub, element] + window.update_schematics() problem_prefix = @element_id.replace(/problem_/,'') @@ -23,7 +25,11 @@ class @Problem @$('section.action input.reset').click @reset @$('section.action input.show').click @show @$('section.action input.save').click @save - @$('input.math').keyup(@refreshMath).each(@refreshMath) + + # Dynamath + @$('input.math').keyup(@refreshMath) + @$('input.math').each (index, element) => + MathJax.Hub.Queue [@refreshMath, null, element] updateProgress: (response) => if response.progress_changed @@ -262,7 +268,9 @@ class @Problem showMethod = @inputtypeShowAnswerMethods[cls] showMethod(inputtype, display, answers) if showMethod? - MathJax.Hub.Queue ["Typeset", MathJax.Hub] + @el.find('.problem > div').each (index, element) => + MathJax.Hub.Queue ["Typeset", MathJax.Hub, element] + @$('.show').val 'Hide Answer' @el.addClass 'showed' @updateProgress response @@ -296,12 +304,21 @@ class @Problem refreshMath: (event, element) => element = event.target unless element - target = "display_#{element.id.replace(/^input_/, '')}" + elid = element.id.replace(/^input_/,'') + target = "display_" + elid + + # MathJax preprocessor is loaded by 'setupInputTypes' + preprocessor_tag = "inputtype_" + elid + mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag] if jax = MathJax.Hub.getAllJax(target)[0] - MathJax.Hub.Queue ['Text', jax, $(element).val()], - [@updateMathML, jax, element] + eqn = $(element).val() + if mathjax_preprocessor + eqn = mathjax_preprocessor(eqn) + MathJax.Hub.Queue(['Text', jax, eqn], [@updateMathML, jax, element]) + return # Explicit return for CoffeeScript + updateMathML: (jax, element) => try $("##{element.id}_dynamath").val(jax.root.toMathML '') @@ -317,6 +334,22 @@ class @Problem @answers = @inputs.serialize() inputtypeSetupMethods: + + 'text-input-dynamath': (element) => + ### + Return: function (eqn) -> eqn that preprocesses the user formula input before + it is fed into MathJax. Return 'false' if no preprocessor specified + ### + data = $(element).find('.text-input-dynamath_data') + + preprocessorClassName = data.data('preprocessor') + preprocessorClass = window[preprocessorClassName] + if not preprocessorClass? + return false + else + preprocessor = new preprocessorClass() + return preprocessor.fn + javascriptinput: (element) => data = $(element).find(".javascriptinput_data") diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js index e07b98d63c..b01f6e12e8 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js +++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js @@ -2023,7 +2023,16 @@ function add_schematic_handler(other_onload) { update_schematics(); } } -window.onload = add_schematic_handler(window.onload); +/* + * THK: Attaching update_schematic to window.onload is rather presumptuous... + * The function is called for EVERY page load, whether in courseware or in + * course info, in 6.002x or the public health course. It is also redundant + * because courseware includes an explicit call to update_schematic after + * each ajax exchange. In this case, calling update_schematic twice appears + * to contribute to a bug in Firefox that does not render the schematic + * properly depending on timing. + */ +//window.onload = add_schematic_handler(window.onload); // ask each schematic input widget to update its value field for submission function prepare_schematics() { diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index c3bbc1e508..9d51ab7207 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -29,6 +29,9 @@ INVALID_CHARS = re.compile(r"[^\w.-]") # Names are allowed to have colons. INVALID_CHARS_NAME = re.compile(r"[^\w.:-]") +# html ids can contain word chars and dashes +INVALID_HTML_CHARS = re.compile(r"[^\w-]") + _LocationBase = namedtuple('LocationBase', 'tag org course category name revision') @@ -44,12 +47,35 @@ class Location(_LocationBase): ''' __slots__ = () + @staticmethod + def _clean(value, invalid): + """ + invalid should be a compiled regexp of chars to replace with '_' + """ + return re.sub('_+', '_', invalid.sub('_', value)) + + @staticmethod def clean(value): """ Return value, made into a form legal for locations """ - return re.sub('_+', '_', INVALID_CHARS.sub('_', value)) + return Location._clean(value, INVALID_CHARS) + + @staticmethod + def clean_for_url_name(value): + """ + Convert value into a format valid for location names (allows colons). + """ + return Location._clean(value, INVALID_CHARS_NAME) + + @staticmethod + def clean_for_html(value): + """ + Convert a string into a form that's safe for use in html ids, classes, urls, etc. + Replaces all INVALID_HTML_CHARS with '_', collapses multiple '_' chars + """ + return Location._clean(value, INVALID_HTML_CHARS) @staticmethod def is_valid(value): @@ -183,9 +209,9 @@ class Location(_LocationBase): Return a string with a version of the location that is safe for use in html id attributes """ - # TODO: is ':' ok in html ids? - return "-".join(str(v) for v in self.list() - if v is not None).replace('.', '_') + s = "-".join(str(v) for v in self.list() + if v is not None) + return Location.clean_for_html(s) def dict(self): """ diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_location.py b/common/lib/xmodule/xmodule/modulestore/tests/test_location.py index 529b1f88eb..afe5e47d10 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_location.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_location.py @@ -114,12 +114,44 @@ def test_equality(): Location('tag', 'org', 'course', 'category', 'name') ) +# All the cleaning functions should do the same thing with these +general_pairs = [ ('',''), + (' ', '_'), + ('abc,', 'abc_'), + ('ab fg!@//\\aj', 'ab_fg_aj'), + (u"ab\xA9", "ab_"), # no unicode allowed for now + ] + def test_clean(): - pairs = [ ('',''), - (' ', '_'), - ('abc,', 'abc_'), - ('ab fg!@//\\aj', 'ab_fg_aj'), - (u"ab\xA9", "ab_"), # no unicode allowed for now - ] + pairs = general_pairs + [ + ('a:b', 'a_b'), # no colons in non-name components + ('a-b', 'a-b'), # dashes ok + ('a.b', 'a.b'), # dot ok + ] for input, output in pairs: assert_equals(Location.clean(input), output) + + +def test_clean_for_url_name(): + pairs = general_pairs + [ + ('a:b', 'a:b'), # colons ok in names + ('a-b', 'a-b'), # dashes ok in names + ('a.b', 'a.b'), # dot ok in names + ] + for input, output in pairs: + assert_equals(Location.clean_for_url_name(input), output) + + +def test_clean_for_html(): + pairs = general_pairs + [ + ("a:b", "a_b"), # no colons for html use + ("a-b", "a-b"), # dashes ok (though need to be replaced in various use locations. ugh.) + ('a.b', 'a_b'), # no dots. + ] + for input, output in pairs: + assert_equals(Location.clean_for_html(input), output) + + +def test_html_id(): + loc = Location("tag://org/course/cat/name:more_name@rev") + assert_equals(loc.html_id(), "tag-org-course-cat-name_more_name-rev") diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 65f692957c..b05ea36e50 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -75,7 +75,7 @@ class SequenceModule(XModule): contents = [] for child in self.get_display_items(): progress = child.get_progress() - contents.append({ + childinfo = { 'content': child.get_html(), 'title': "\n".join( grand_child.display_name.strip() @@ -85,7 +85,10 @@ class SequenceModule(XModule): 'progress_status': Progress.to_js_status_str(progress), 'progress_detail': Progress.to_js_detail_str(progress), 'type': child.get_icon_class(), - }) + } + if childinfo['title']=='': + childinfo['title'] = child.metadata.get('display_name','') + contents.append(childinfo) params = {'items': contents, 'element_id': self.location.html_id(), diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index 4103a7373e..654b6beb15 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -29,7 +29,7 @@ from nose.plugins.skip import SkipTest from mock import Mock i4xs = ModuleSystem( - ajax_url='/', + ajax_url='courses/course_id/modx/a_location', track_function=Mock(), get_module=Mock(), render_template=Mock(), diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index 547df36471..a5b3460f17 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -28,7 +28,7 @@ def is_pointer_tag(xml_obj): No children, one attribute named url_name. Special case for course roots: the pointer is - + xml_obj: an etree Element diff --git a/common/static/images/spinner-on-grey.gif b/common/static/images/spinner-on-grey.gif new file mode 100644 index 0000000000..f43d52e4f4 Binary files /dev/null and b/common/static/images/spinner-on-grey.gif differ diff --git a/common/static/images/spinner.gif b/common/static/images/spinner.gif index b2f94cd12c..e23eb78abe 100644 Binary files a/common/static/images/spinner.gif and b/common/static/images/spinner.gif differ diff --git a/common/static/js/vendor/flot/jquery.flot.axislabels.js b/common/static/js/vendor/flot/jquery.flot.axislabels.js new file mode 100644 index 0000000000..797f82ec9f --- /dev/null +++ b/common/static/js/vendor/flot/jquery.flot.axislabels.js @@ -0,0 +1,412 @@ +/* +Axis Labels Plugin for flot. +http://github.com/markrcote/flot-axislabels + +Original code is Copyright (c) 2010 Xuan Luo. +Original code was released under the GPLv3 license by Xuan Luo, September 2010. +Original code was rereleased under the MIT license by Xuan Luo, April 2012. + +Improvements by Mark Cote. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ +(function ($) { + var options = { }; + + function canvasSupported() { + return !!document.createElement('canvas').getContext; + } + + function canvasTextSupported() { + if (!canvasSupported()) { + return false; + } + var dummy_canvas = document.createElement('canvas'); + var context = dummy_canvas.getContext('2d'); + return typeof context.fillText == 'function'; + } + + function css3TransitionSupported() { + var div = document.createElement('div'); + return typeof div.style.MozTransition != 'undefined' // Gecko + || typeof div.style.OTransition != 'undefined' // Opera + || typeof div.style.webkitTransition != 'undefined' // WebKit + || typeof div.style.transition != 'undefined'; + } + + + function AxisLabel(axisName, position, padding, plot, opts) { + this.axisName = axisName; + this.position = position; + this.padding = padding; + this.plot = plot; + this.opts = opts; + this.width = 0; + this.height = 0; + } + + + CanvasAxisLabel.prototype = new AxisLabel(); + CanvasAxisLabel.prototype.constructor = CanvasAxisLabel; + function CanvasAxisLabel(axisName, position, padding, plot, opts) { + AxisLabel.prototype.constructor.call(this, axisName, position, padding, + plot, opts); + } + + CanvasAxisLabel.prototype.calculateSize = function() { + if (!this.opts.axisLabelFontSizePixels) + this.opts.axisLabelFontSizePixels = 14; + if (!this.opts.axisLabelFontFamily) + this.opts.axisLabelFontFamily = 'sans-serif'; + + var textWidth = this.opts.axisLabelFontSizePixels + this.padding; + var textHeight = this.opts.axisLabelFontSizePixels + this.padding; + if (this.position == 'left' || this.position == 'right') { + this.width = this.opts.axisLabelFontSizePixels + this.padding; + this.height = 0; + } else { + this.width = 0; + this.height = this.opts.axisLabelFontSizePixels + this.padding; + } + }; + + CanvasAxisLabel.prototype.draw = function(box) { + var ctx = this.plot.getCanvas().getContext('2d'); + ctx.save(); + ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + + this.opts.axisLabelFontFamily; + var width = ctx.measureText(this.opts.axisLabel).width; + var height = this.opts.axisLabelFontSizePixels; + var x, y, angle = 0; + if (this.position == 'top') { + x = box.left + box.width/2 - width/2; + y = box.top + height*0.72; + } else if (this.position == 'bottom') { + x = box.left + box.width/2 - width/2; + y = box.top + box.height - height*0.72; + } else if (this.position == 'left') { + x = box.left + height*0.72; + y = box.height/2 + box.top + width/2; + angle = -Math.PI/2; + } else if (this.position == 'right') { + x = box.left + box.width - height*0.72; + y = box.height/2 + box.top - width/2; + angle = Math.PI/2; + } + ctx.translate(x, y); + ctx.rotate(angle); + ctx.fillText(this.opts.axisLabel, 0, 0); + ctx.restore(); + }; + + + HtmlAxisLabel.prototype = new AxisLabel(); + HtmlAxisLabel.prototype.constructor = HtmlAxisLabel; + function HtmlAxisLabel(axisName, position, padding, plot, opts) { + AxisLabel.prototype.constructor.call(this, axisName, position, + padding, plot, opts); + } + + HtmlAxisLabel.prototype.calculateSize = function() { + var elem = $('
' + + this.opts.axisLabel + '
'); + this.plot.getPlaceholder().append(elem); + // store height and width of label itself, for use in draw() + this.labelWidth = elem.outerWidth(true); + this.labelHeight = elem.outerHeight(true); + elem.remove(); + + this.width = this.height = 0; + if (this.position == 'left' || this.position == 'right') { + this.width = this.labelWidth + this.padding; + } else { + this.height = this.labelHeight + this.padding; + } + }; + + HtmlAxisLabel.prototype.draw = function(box) { + this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove(); + var elem = $('
' + + this.opts.axisLabel + '
'); + this.plot.getPlaceholder().append(elem); + if (this.position == 'top') { + elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px'); + elem.css('top', box.top + 'px'); + } else if (this.position == 'bottom') { + elem.css('left', box.left + box.width/2 - this.labelWidth/2 + 'px'); + elem.css('top', box.top + box.height - this.labelHeight + 'px'); + } else if (this.position == 'left') { + elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px'); + elem.css('left', box.left + 'px'); + } else if (this.position == 'right') { + elem.css('top', box.top + box.height/2 - this.labelHeight/2 + 'px'); + elem.css('left', box.left + box.width - this.labelWidth + 'px'); + } + }; + + + CssTransformAxisLabel.prototype = new HtmlAxisLabel(); + CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel; + function CssTransformAxisLabel(axisName, position, padding, plot, opts) { + HtmlAxisLabel.prototype.constructor.call(this, axisName, position, + padding, plot, opts); + } + + CssTransformAxisLabel.prototype.calculateSize = function() { + HtmlAxisLabel.prototype.calculateSize.call(this); + this.width = this.height = 0; + if (this.position == 'left' || this.position == 'right') { + this.width = this.labelHeight + this.padding; + } else { + this.height = this.labelHeight + this.padding; + } + }; + + CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) { + var stransforms = { + '-moz-transform': '', + '-webkit-transform': '', + '-o-transform': '', + '-ms-transform': '' + }; + if (x != 0 || y != 0) { + var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)'; + stransforms['-moz-transform'] += stdTranslate; + stransforms['-webkit-transform'] += stdTranslate; + stransforms['-o-transform'] += stdTranslate; + stransforms['-ms-transform'] += stdTranslate; + } + if (degrees != 0) { + var rotation = degrees / 90; + var stdRotate = ' rotate(' + degrees + 'deg)'; + stransforms['-moz-transform'] += stdRotate; + stransforms['-webkit-transform'] += stdRotate; + stransforms['-o-transform'] += stdRotate; + stransforms['-ms-transform'] += stdRotate; + } + var s = 'top: 0; left: 0; '; + for (var prop in stransforms) { + if (stransforms[prop]) { + s += prop + ':' + stransforms[prop] + ';'; + } + } + s += ';'; + return s; + }; + + CssTransformAxisLabel.prototype.calculateOffsets = function(box) { + var offsets = { x: 0, y: 0, degrees: 0 }; + if (this.position == 'bottom') { + offsets.x = box.left + box.width/2 - this.labelWidth/2; + offsets.y = box.top + box.height - this.labelHeight; + } else if (this.position == 'top') { + offsets.x = box.left + box.width/2 - this.labelWidth/2; + offsets.y = box.top; + } else if (this.position == 'left') { + offsets.degrees = -90; + offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2; + offsets.y = box.height/2 + box.top; + } else if (this.position == 'right') { + offsets.degrees = 90; + offsets.x = box.left + box.width - this.labelWidth/2 + - this.labelHeight/2; + offsets.y = box.height/2 + box.top; + } + return offsets; + }; + + CssTransformAxisLabel.prototype.draw = function(box) { + this.plot.getPlaceholder().find("." + this.axisName + "Label").remove(); + var offsets = this.calculateOffsets(box); + var elem = $('
' + this.opts.axisLabel + '
'); + this.plot.getPlaceholder().append(elem); + }; + + + IeTransformAxisLabel.prototype = new CssTransformAxisLabel(); + IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel; + function IeTransformAxisLabel(axisName, position, padding, plot, opts) { + CssTransformAxisLabel.prototype.constructor.call(this, axisName, + position, padding, + plot, opts); + this.requiresResize = false; + } + + IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) { + // I didn't feel like learning the crazy Matrix stuff, so this uses + // a combination of the rotation transform and CSS positioning. + var s = ''; + if (degrees != 0) { + var rotation = degrees/90; + while (rotation < 0) { + rotation += 4; + } + s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); '; + // see below + this.requiresResize = (this.position == 'right'); + } + if (x != 0) { + s += 'left: ' + x + 'px; '; + } + if (y != 0) { + s += 'top: ' + y + 'px; '; + } + return s; + }; + + IeTransformAxisLabel.prototype.calculateOffsets = function(box) { + var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call( + this, box); + // adjust some values to take into account differences between + // CSS and IE rotations. + if (this.position == 'top') { + // FIXME: not sure why, but placing this exactly at the top causes + // the top axis label to flip to the bottom... + offsets.y = box.top + 1; + } else if (this.position == 'left') { + offsets.x = box.left; + offsets.y = box.height/2 + box.top - this.labelWidth/2; + } else if (this.position == 'right') { + offsets.x = box.left + box.width - this.labelHeight; + offsets.y = box.height/2 + box.top - this.labelWidth/2; + } + return offsets; + }; + + IeTransformAxisLabel.prototype.draw = function(box) { + CssTransformAxisLabel.prototype.draw.call(this, box); + if (this.requiresResize) { + var elem = this.plot.getPlaceholder().find("." + this.axisName + "Label"); + // Since we used CSS positioning instead of transforms for + // translating the element, and since the positioning is done + // before any rotations, we have to reset the width and height + // in case the browser wrapped the text (specifically for the + // y2axis). + elem.css('width', this.labelWidth); + elem.css('height', this.labelHeight); + } + }; + + + function init(plot) { + // This is kind of a hack. There are no hooks in Flot between + // the creation and measuring of the ticks (setTicks, measureTickLabels + // in setupGrid() ) and the drawing of the ticks and plot box + // (insertAxisLabels in setupGrid() ). + // + // Therefore, we use a trick where we run the draw routine twice: + // the first time to get the tick measurements, so that we can change + // them, and then have it draw it again. + var secondPass = false; + + var axisLabels = {}; + var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 }; + + var defaultPadding = 2; // padding between axis and tick labels + plot.hooks.draw.push(function (plot, ctx) { + if (!secondPass) { + // MEASURE AND SET OPTIONS + $.each(plot.getAxes(), function(axisName, axis) { + var opts = axis.options // Flot 0.7 + || plot.getOptions()[axisName]; // Flot 0.6 + if (!opts || !opts.axisLabel || !axis.show) + return; + + var renderer = null; + + if (!opts.axisLabelUseHtml && + navigator.appName == 'Microsoft Internet Explorer') { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) { + rv = parseFloat(RegExp.$1); + } + if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { + renderer = CssTransformAxisLabel; + } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { + renderer = IeTransformAxisLabel; + } else if (opts.axisLabelUseCanvas) { + renderer = CanvasAxisLabel; + } else { + renderer = HtmlAxisLabel; + } + } else { + if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) { + renderer = HtmlAxisLabel; + } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) { + renderer = CanvasAxisLabel; + } else { + renderer = CssTransformAxisLabel; + } + } + + var padding = opts.axisLabelPadding === undefined ? + defaultPadding : opts.axisLabelPadding; + + axisLabels[axisName] = new renderer(axisName, + axis.position, padding, + plot, opts); + + // flot interprets axis.labelHeight and .labelWidth as + // the height and width of the tick labels. We increase + // these values to make room for the axis label and + // padding. + + axisLabels[axisName].calculateSize(); + + // AxisLabel.height and .width are the size of the + // axis label and padding. + axis.labelHeight += axisLabels[axisName].height; + axis.labelWidth += axisLabels[axisName].width; + opts.labelHeight = axis.labelHeight; + opts.labelWidth = axis.labelWidth; + }); + // re-draw with new label widths and heights + secondPass = true; + plot.setupGrid(); + plot.draw(); + } else { + // DRAW + $.each(plot.getAxes(), function(axisName, axis) { + var opts = axis.options // Flot 0.7 + || plot.getOptions()[axisName]; // Flot 0.6 + if (!opts || !opts.axisLabel || !axis.show) + return; + + axisLabels[axisName].draw(axis.box); + }); + } + }); + } + + + $.plot.plugins.push({ + init: init, + options: options, + name: 'axisLabels', + version: '2.0b0' + }); +})(jQuery); diff --git a/doc/discussion.md b/doc/discussion.md index 4f8ab9a01a..2446485497 100644 --- a/doc/discussion.md +++ b/doc/discussion.md @@ -9,7 +9,7 @@ If you haven't done so already: brew install mongodb Make sure that you have mongodb running. You can simply open a new terminal tab and type: - + mongod ## Installing elasticsearch @@ -72,9 +72,9 @@ For convenience, add the following environment variables to the terminal (assumi export DJANGO_SETTINGS_MODULE=lms.envs.dev export PYTHONPATH=. -Now initialzie roles and permissions: +Now initialzie roles and permissions, providing a course id eg.: - django-admin.py seed_permissions_roles + django-admin.py seed_permissions_roles "MITx/6.002x/2012_Fall" To assign yourself as a moderator, use the following command (assuming your username is "test", and the course id is "MITx/6.002x/2012_Fall"): diff --git a/doc/xml-format.md b/doc/xml-format.md index e1439ee97d..29c60fea99 100644 --- a/doc/xml-format.md +++ b/doc/xml-format.md @@ -299,7 +299,7 @@ This is a sketch ("tue" is not a valid start date), that should help illustrate ## Specifying metadata in the xml file -Metadata can also live in the xml files, but anything defined in the policy file overrides anything in the xml. This is primarily for backwards compatibility, and you should probably not use both. If you do leave some metadata tags in the xml, you should be consistent (e.g. if `display_name`s stay in xml, they should all stay in xml). +Metadata can also live in the xml files, but anything defined in the policy file overrides anything in the xml. This is primarily for backwards compatibility, and you should probably not use both. If you do leave some metadata tags in the xml, you should be consistent (e.g. if `display_name`s stay in xml, they should all stay in xml. Note `display_name` should be specified in the problem xml definition itself, ie, Problem Text , in file ProblemFoo.xml). - note, some xml attributes are not metadata. e.g. in `
+ + + ## For now, ocw links are the only thing that goes in additional resources + % if get_course_about_section(course, "ocw_links"): +
+
+

Additional Resources

+
+ +
+

MITOpenCourseware

+ ${get_course_about_section(course, "ocw_links")} +
+
+ %endif
diff --git a/lms/templates/press.json b/lms/templates/press.json index 16942f06fb..e4aeb48675 100644 --- a/lms/templates/press.json +++ b/lms/templates/press.json @@ -1,4 +1,196 @@ [ + + { + "title": "College may never be the same", + "url": "http://www.usatoday.com/news/nation/story/2012/09/12/college-may-never-be-the-same/57752972/1", + "author": "Mary Beth Marklein", + "image": "usa_logo_178x138.jpeg", + "deck": "", + "publication": "USA Today", + "publish_date": "September 12, 2012" + }, + { + "title": "Is MIT Giving Away the Farm?", + "url": "http://www.technologyreview.com/mitnews/428698/is-mit-giving-away-the-farm/", + "author": "Larry Hardesty", + "image": "techreview_logo_178x138.jpg", + "deck": "The surprising logic of MIT's free online education program.", + "publication": "Technology Review", + "publish_date": "September/October 2012" + }, + { + "title": "School’s Out, Forever", + "url": "http://www.bostonmagazine.com/articles/2012/08/edx-online-classes-schools-ou
t-forever/", + "author": "Chris Vogel", + "image": "bostonmag_logo_178x138.jpg", + "deck": "A new online education program from Harvard and MIT is poised to transform what it means to go to college.", + "publication": "Boston Magazine", + "publish_date": "September 2012" + }, + { + "title": "Q&A: Anant Agarwal, edX’s president and first professor", + "url": "http://www.bostonmagazine.com/articles/2012/08/edx-online-classes-schools-ou
t-forever/", + "author": " Molly Petrilla ", + "image": "smartplanet_logo_178x138.jpg", + "deck": "", + "publication": "Smart Planet", + "publish_date": "September 3, 2012" + }, + + { + "title": "EdX To Offer Proctored Final Exam For One Course", + "url": "http://www.thecrimson.com/article/2012/9/7/edx-offer-proctored-exams/", + "author": "Samuel Y. Weinstock", + "image": "harvardcrimson_logo_178x138.jpeg", + "deck": "", + "publication": "Harvard Crimson", + "publish_date": "September 7, 2012" + }, + { + "title": "MOOCing On Site", + "url": "http://www.insidehighered.com/news/2012/09/07/site-based-testing-deals-strengthen-case-granting-credit-mooc-students", + "author": "Steve Kolowich ", + "image": "insidehighered_logo_178x138.jpg", + "deck": "", + "publication": "Inside Higher Education", + "publish_date": "September 7, 2012" + }, + { + "title": "edX Curbs the Downfalls of Online Education By Announcing Supervised Final Exams", + "url": "http://bostinno.com/2012/09/07/edx-pearson-proctored-exams/", + "author": "Lauren Landry", + "image": "bostinno_logo_178x138.jpg", + "deck": "", + "publication": "Bostinno", + "publish_date": "September 7, 2012" + }, + { + "title": "Harvard and MIT online courses get 'real world' exams", + "url": "http://www.bbc.co.uk/news/education-19505776", + "author": "Sean Coughlan", + "image": "bbc_logo_178x138.jpeg", + "deck": "", + "publication": "BBC", + "publish_date": "September 6, 2012" + }, + { + "title": "Harvard-MIT Online School EdX to Offer Supervised Final Exams", + "url": "http://www.businessweek.com/news/2012-09-06/harvard-mit-online-school-edx-to-offer-supervised-final-exams", + "author": "Oliver Staley", + "image": "bloomberg_logo_178x138.jpeg", + "deck": "", + "publication": "Bloomberg Business Week", + "publish_date": "September 6, 2012" + }, + { + "title": "Colorado State to Offer Credits for Online Class", + "url": "http://www.nytimes.com/2012/09/07/education/colorado-state-to-offer-credits-for-online-class.html?_r=3", + "author": "Tamar Lewin", + "image": "nyt_logo_178x138.jpeg", + "deck": "", + "publication": "New York Times", + "publish_date": "September 6, 2012" + }, + { + "title": "edX Offers Proctored Exams for Open Online Course", + "url": "http://chronicle.com/blogs/wiredcampus/edx-offers-proctored-exams-for-open-online-course/39656", + "author": " Marc Parry", + "image": "chroniclehighered_logo_178x138.jpeg", + "deck": "", + "publication": "Chronicle of Higher Education", + "publish_date": "September 6, 2012" + }, + { + "title": "edX Offers Proctored Exams for Open Online Course", + "url": "http://itbriefing.net/modules.php?op=modload&name=News&file=article&sid=323229&newlang=eng&topic=15&catid=37", + "author": "", + "image": "itbriefing_logo_178x138.jpg", + "deck": "", + "publication": "ITBriefing.net", + "publish_date": "September 6, 2012" + }, + + { + "title": "Student Loans: Debt for Life", + "url": "http://www.businessweek.com/articles/2012-09-06/student-loans-debt-for-life#p3", + "author": "Peter Coy", + "image": "bloomberg_logo_178x138.jpeg", + "deck": "", + "publication": "Bloomberg Business Week", + "publish_date": "September 6, 2012" + }, + { + "title": "Straighterline wants to help professors expand reach, while students save", + "url": "http://www.baltimoresun.com/business/technology/blog/bs-bz-straighterline-college-professors-20120904,0,6114022.story", + "author": "Gus G. Sentementes", + "image": "baltsun_logo_178x138.jpg", + "deck": "", + "publication": "The Baltimore Sun", + "publish_date": "September 4, 2012" + }, + { + "title": "Want to be a reporter? Learn to code", + "url": "http://gigaom.com/cloud/want-to-be-a-reporter-learn-to-code/", + "author": "Barb Darrow", + "image": "gigaom_logo_178x138.jpeg", + "deck": "", + "publication": "GigaOM", + "publish_date": "September 4, 2012" + }, + { + "title": "MOOC Brigade: Will Massive, Open Online Courses Revolutionize Higher Education?", + "url": "http://nation.time.com/2012/09/04/mooc-brigade-will-massive-open-online-courses-revolutionize-higher-education/", + "author": "Kayla Webley", + "image": "time_logo_178x138.jpg", + "deck": "", + "publication": "Time", + "publish_date": "September 4, 2012" + }, + { + "title": "Ivy walls lower with free online classes from Coursera and edX ", + "url": "http://www.csmonitor.com/Innovation/Pioneers/2012/0903/Ivy-walls-lower-with-free-online-classes-from-Coursera-and-edX", + "author": "Chris Gaylord", + "image": "csmonitor_logo_178x138.jpg", + "deck": "", + "publication": "Christian Science Monitor", + "publish_date": "September 3, 2012" + }, + { + "title": "Summer recap. RLADs, new edX partner, Institute files amicus brief", + "url": "http://tech.mit.edu/V132/N34/summer.html", + "author": "", + "image": "thetech_logo_178x138.jpg", + "deck": "", + "publication": "The Tech", + "publish_date": "September 4, 2012" + }, + { + "title": "Into the Future With MOOC's", + "url": "http://chronicle.com/article/Into-the-Future-With-MOOCs/134080/", + "author": "Kevin Carey", + "image": "chroniclehighered_logo_178x138.jpeg", + "deck": "", + "publication": "The Chronicle of Higher Education", + "publish_date": "September 3, 2012" + }, + { + "title": "The Future Of Higher Education", + "url": "http://radioboston.wbur.org/2012/08/20/higher-education-online", + "author": "", + "image": "radioboston_logo_178x138.jpg", + "deck": "", + "publication": "NPR/Radio Boston", + "publish_date": "August 20, 2012" + }, + { + "title": "Berkeley Joins edX", + "url": "http://www.insidehighered.com/quicktakes/2012/07/24/berkeley-joins-edx", + "author": "Tamar Lewin", + "image": "insidehighered_logo_178x138.jpg", + "deck": "", + "publication": "Inside Higher Ed", + "publish_date": "July 24, 2012" + }, { "title": "Berkeley to Join the Free Online Learning Partnership EdX", "url": "http://www.nytimes.com/2012/07/24/education/berkeley-to-offer-free-online-classes-on-edx.html?_r=1", diff --git a/lms/templates/signup_modal.html b/lms/templates/signup_modal.html index 1510eb407b..96b2e33ac3 100644 --- a/lms/templates/signup_modal.html +++ b/lms/templates/signup_modal.html @@ -21,18 +21,18 @@
% if has_extauth_info is UNDEFINED: - + - + - + - + % else:

Welcome ${extauth_email}


Enter a public username:

- + % endif
@@ -75,9 +75,9 @@ - + - + diff --git a/lms/templates/static_templates/press.html b/lms/templates/static_templates/press.html index 6294b346a9..277cb91bd2 100644 --- a/lms/templates/static_templates/press.html +++ b/lms/templates/static_templates/press.html @@ -37,4 +37,3 @@ % endfor - diff --git a/lms/templates/wiki/base.html b/lms/templates/wiki/base.html index 8a9686f546..22b107104b 100644 --- a/lms/templates/wiki/base.html +++ b/lms/templates/wiki/base.html @@ -31,6 +31,9 @@ {% addtoblock 'js' %} {% comment %} These scripts load at the bottom of the body {% endcomment %} + diff --git a/lms/templates/wiki/preview_inline.html b/lms/templates/wiki/preview_inline.html index a5c6668d16..2f09d21bd3 100644 --- a/lms/templates/wiki/preview_inline.html +++ b/lms/templates/wiki/preview_inline.html @@ -41,6 +41,10 @@ {% compressed_js 'application' %} {% compressed_js 'module-js' %} + + {% with mathjax_mode='wiki' %} {% include "mathjax_include.html" %} {% endwith %} diff --git a/lms/urls.py b/lms/urls.py index 8484ccc40b..49febaf84e 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -237,6 +237,7 @@ if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'): urlpatterns += ( url(r'^migrate/modules$', 'lms_migration.migrate.manage_modulestores'), url(r'^migrate/reload/(?P[^/]+)$', 'lms_migration.migrate.manage_modulestores'), + url(r'^migrate/reload/(?P[^/]+)/(?P[^/]+)$', 'lms_migration.migrate.manage_modulestores'), url(r'^gitreload$', 'lms_migration.migrate.gitreload'), url(r'^gitreload/(?P[^/]+)$', 'lms_migration.migrate.gitreload'), )