Commit 66e443ae by Your Name

Merge github.com:MITx/mitx into feature/kevin/groups_ui_changes

parents 043d9623 ab437b94
...@@ -8,6 +8,8 @@ from django.core.urlresolvers import reverse ...@@ -8,6 +8,8 @@ from django.core.urlresolvers import reverse
from path import path from path import path
from tempfile import mkdtemp from tempfile import mkdtemp
import json import json
from fs.osfs import OSFS
from student.models import Registration from student.models import Registration
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -350,6 +352,33 @@ class ContentStoreTest(TestCase): ...@@ -350,6 +352,33 @@ class ContentStoreTest(TestCase):
def test_edit_unit_full(self): def test_edit_unit_full(self):
self.check_edit_unit('full') self.check_edit_unit('full')
def test_static_tab_reordering(self):
import_from_xml(modulestore(), 'common/test/data/', ['full'])
ms = modulestore('direct')
course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None]))
# reverse the ordering
reverse_tabs = []
for tab in course.tabs:
if tab['type'] == 'static_tab':
reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs':reverse_tabs}), "application/json")
course = ms.get_item(Location(['i4x','edX','full','course','6.002_Spring_2012', None]))
# compare to make sure that the tabs information is in the expected order after the server call
course_tabs = []
for tab in course.tabs:
if tab['type'] == 'static_tab':
course_tabs.append('i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
self.assertEqual(reverse_tabs, course_tabs)
def test_about_overrides(self): def test_about_overrides(self):
''' '''
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
...@@ -430,6 +459,28 @@ class ContentStoreTest(TestCase): ...@@ -430,6 +459,28 @@ class ContentStoreTest(TestCase):
# export out to a tempdir # export out to a tempdir
export_to_xml(ms, cs, location, root_dir, 'test_export') export_to_xml(ms, cs, location, root_dir, 'test_export')
# check for static tabs
fs = OSFS(root_dir / 'test_export')
self.assertTrue(fs.exists('tabs'))
static_tabs_query_loc = Location('i4x', location.org, location.course, 'static_tab', None)
static_tabs = ms.get_items(static_tabs_query_loc)
for static_tab in static_tabs:
fs = OSFS(root_dir / 'test_export/tabs')
self.assertTrue(fs.exists(static_tab.location.name + '.html'))
# check for custom_tags
fs = OSFS(root_dir / 'test_export')
self.assertTrue(fs.exists('custom_tags'))
custom_tags_query_loc = Location('i4x', location.org, location.course, 'custom_tag_template', None)
custom_tags = ms.get_items(custom_tags_query_loc)
for custom_tag in custom_tags:
fs = OSFS(root_dir / 'test_export/custom_tags')
self.assertTrue(fs.exists(custom_tag.location.name))
# remove old course # remove old course
delete_course(ms, cs, location) delete_course(ms, cs, location)
......
...@@ -15,7 +15,7 @@ class CMS.Views.TabsEdit extends Backbone.View ...@@ -15,7 +15,7 @@ class CMS.Views.TabsEdit extends Backbone.View
@$('.components').sortable( @$('.components').sortable(
handle: '.drag-handle' handle: '.drag-handle'
update: (event, ui) => alert 'not yet implemented!' update: @tabMoved
helper: 'clone' helper: 'clone'
opacity: '0.5' opacity: '0.5'
placeholder: 'component-placeholder' placeholder: 'component-placeholder'
...@@ -24,6 +24,20 @@ class CMS.Views.TabsEdit extends Backbone.View ...@@ -24,6 +24,20 @@ class CMS.Views.TabsEdit extends Backbone.View
items: '> .component' items: '> .component'
) )
tabMoved: (event, ui) =>
tabs = []
@$('.component').each((idx, element) =>
tabs.push($(element).data('id'))
)
$.ajax({
type:'POST',
url: '/reorder_static_tabs',
data: JSON.stringify({
tabs : tabs
}),
contentType: 'application/json'
})
addNewTab: (event) => addNewTab: (event) =>
event.preventDefault() event.preventDefault()
......
...@@ -68,10 +68,12 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ ...@@ -68,10 +68,12 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
save_videosource: function(newsource) { save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string // newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1 // returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null}); if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null},
{ error : CMS.ServerError});
// TODO remove all whitespace w/in string // TODO remove all whitespace w/in string
else { else {
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource); if (this.get('intro_video') !== newsource) this.save('intro_video', newsource,
{ error : CMS.ServerError});
} }
return this.videosourceSample(); return this.videosourceSample();
......
...@@ -99,10 +99,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -99,10 +99,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
var targetModel = this.eventModel(event); var targetModel = this.eventModel(event);
targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() }); targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() });
// push change to display, hide the editor, submit the change // push change to display, hide the editor, submit the change
targetModel.save({}, {error : function(model, xhr) { targetModel.save({}, {error : CMS.ServerError});
// TODO use a standard component
window.alert(xhr.responseText);
}});
this.closeEditor(this); this.closeEditor(this);
}, },
...@@ -145,8 +142,10 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ ...@@ -145,8 +142,10 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
this.modelDom(event).remove(); this.modelDom(event).remove();
var cacheThis = this; var cacheThis = this;
targetModel.destroy({success : function (model, response) { targetModel.destroy({success : function (model, response) {
cacheThis.collection.fetch({success : function() {cacheThis.render();}}); cacheThis.collection.fetch({success : function() {cacheThis.render();},
} error : CMS.ServerError});
},
error : CMS.ServerError
}); });
}, },
...@@ -225,7 +224,8 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({ ...@@ -225,7 +224,8 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
self.render(); self.render();
} }
); );
} },
error : CMS.ServerError
} }
); );
}, },
...@@ -267,7 +267,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({ ...@@ -267,7 +267,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
onSave: function(event) { onSave: function(event) {
this.model.set('data', this.$codeMirror.getValue()); this.model.set('data', this.$codeMirror.getValue());
this.render(); this.render();
this.model.save(); this.model.save({}, {error: CMS.ServerError});
this.$form.hide(); this.$form.hide();
this.closeEditor(this); this.closeEditor(this);
}, },
......
CMS.ServerError = function(model, error) {
// this handler is for the client:server communication not the validation errors which handleValidationError catches
window.alert("Server Error: " + error.responseText);
};
\ No newline at end of file
...@@ -55,7 +55,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({ ...@@ -55,7 +55,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({
var newVal = $(event.currentTarget).val(); var newVal = $(event.currentTarget).val();
if (currentVal != newVal) { if (currentVal != newVal) {
this.clearValidationErrors(); this.clearValidationErrors();
this.model.save(field, newVal); this.model.save(field, newVal, { error : CMS.ServerError});
return true; return true;
} }
else return false; else return false;
...@@ -227,7 +227,8 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -227,7 +227,8 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
time = 0; time = 0;
} }
var newVal = new Date(date.getTime() + time * 1000); var newVal = new Date(date.getTime() + time * 1000);
if (cacheModel.get(fieldName) != newVal) cacheModel.save(fieldName, newVal); if (cacheModel.get(fieldName) != newVal) cacheModel.save(fieldName, newVal,
{ error : CMS.ServerError});
} }
}; };
...@@ -276,7 +277,8 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -276,7 +277,8 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
}, },
removeSyllabus: function() { removeSyllabus: function() {
if (this.model.has('syllabus')) this.model.save({'syllabus': null}); if (this.model.has('syllabus')) this.model.save({'syllabus': null},
{ error : CMS.ServerError});
}, },
assetSyllabus : function() { assetSyllabus : function() {
...@@ -309,7 +311,8 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({ ...@@ -309,7 +311,8 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
mirror.save(); mirror.save();
cachethis.clearValidationErrors(); cachethis.clearValidationErrors();
var newVal = mirror.getValue(); var newVal = mirror.getValue();
if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal); if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal,
{ error : CMS.ServerError});
} }
}); });
} }
...@@ -404,7 +407,8 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -404,7 +407,8 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
setGracePeriod : function(event) { setGracePeriod : function(event) {
event.data.clearValidationErrors(); event.data.clearValidationErrors();
var newVal = event.data.model.dateToGracePeriod($(event.currentTarget).timepicker('getTime')); var newVal = event.data.model.dateToGracePeriod($(event.currentTarget).timepicker('getTime'));
if (event.data.model.get('grace_period') != newVal) event.data.model.save('grace_period', newVal); if (event.data.model.get('grace_period') != newVal) event.data.model.save('grace_period', newVal,
{ error : CMS.ServerError});
}, },
updateModel : function(event) { updateModel : function(event) {
if (!this.selectorToField[event.currentTarget.id]) return; if (!this.selectorToField[event.currentTarget.id]) return;
...@@ -540,7 +544,8 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({ ...@@ -540,7 +544,8 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
object[cutoff['designation']] = cutoff['cutoff'] / 100.0; object[cutoff['designation']] = cutoff['cutoff'] / 100.0;
return object; return object;
}, },
{})); {}),
{ error : CMS.ServerError});
}, },
addNewGrade: function(e) { addNewGrade: function(e) {
...@@ -671,7 +676,8 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({ ...@@ -671,7 +676,8 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
} }
}, },
deleteModel : function(e) { deleteModel : function(e) {
this.model.destroy(); this.model.destroy(
{ error : CMS.ServerError});
e.preventDefault(); e.preventDefault();
} }
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<script type="text/javascript" src="${static.url('js/models/course_info.js')}"></script> <script type="text/javascript" src="${static.url('js/models/course_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/module_info.js')}"></script> <script type="text/javascript" src="${static.url('js/models/module_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/course_info_edit.js')}"></script> <script type="text/javascript" src="${static.url('js/views/course_info_edit.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script> <script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script src="${static.url('js/vendor/timepicker/datepair.js')}"></script> <script src="${static.url('js/vendor/timepicker/datepair.js')}"></script>
......
...@@ -20,6 +20,7 @@ from contentstore import utils ...@@ -20,6 +20,7 @@ from contentstore import utils
<script type="text/javascript" src="${static.url('js/models/settings/course_settings.js')}"></script> <script type="text/javascript" src="${static.url('js/models/settings/course_settings.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script> <script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/main_settings_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/settings/main_settings_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
......
...@@ -17,6 +17,7 @@ urlpatterns = ('', ...@@ -17,6 +17,7 @@ urlpatterns = ('',
url(r'^publish_draft$', 'contentstore.views.publish_draft', name='publish_draft'), url(r'^publish_draft$', 'contentstore.views.publish_draft', name='publish_draft'),
url(r'^unpublish_unit$', 'contentstore.views.unpublish_unit', name='unpublish_unit'), url(r'^unpublish_unit$', 'contentstore.views.unpublish_unit', name='unpublish_unit'),
url(r'^create_new_course', 'contentstore.views.create_new_course', name='create_new_course'), url(r'^create_new_course', 'contentstore.views.create_new_course', name='create_new_course'),
url(r'^reorder_static_tabs', 'contentstore.views.reorder_static_tabs', name='reorder_static_tabs'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$', url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$',
'contentstore.views.course_index', name='course_index'), 'contentstore.views.course_index', name='course_index'),
......
from optparse import make_option from optparse import make_option
from json import dump from json import dump
from datetime import datetime
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
...@@ -32,10 +33,9 @@ class Command(BaseCommand): ...@@ -32,10 +33,9 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
if len(args) < 1: if len(args) < 1:
raise CommandError("Missing single argument: output JSON file") outputfile = datetime.utcnow().strftime("pearson-dump-%Y%m%d-%H%M%S.json")
else:
# get output location: outputfile = args[0]
outputfile = args[0]
# construct the query object to dump: # construct the query object to dump:
registrations = TestCenterRegistration.objects.all() registrations = TestCenterRegistration.objects.all()
...@@ -65,6 +65,8 @@ class Command(BaseCommand): ...@@ -65,6 +65,8 @@ class Command(BaseCommand):
} }
if len(registration.upload_error_message) > 0: if len(registration.upload_error_message) > 0:
record['registration_error'] = registration.upload_error_message record['registration_error'] = registration.upload_error_message
if len(registration.testcenter_user.upload_error_message) > 0:
record['demographics_error'] = registration.testcenter_user.upload_error_message
if registration.needs_uploading: if registration.needs_uploading:
record['needs_uploading'] = True record['needs_uploading'] = True
...@@ -72,5 +74,5 @@ class Command(BaseCommand): ...@@ -72,5 +74,5 @@ class Command(BaseCommand):
# dump output: # dump output:
with open(outputfile, 'w') as outfile: with open(outputfile, 'w') as outfile:
dump(output, outfile) dump(output, outfile, indent=2)
...@@ -229,6 +229,7 @@ class CapaModule(XModule): ...@@ -229,6 +229,7 @@ class CapaModule(XModule):
'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,
'progress': Progress.to_js_status_str(self.get_progress())
}) })
def get_problem_html(self, encapsulate=True): def get_problem_html(self, encapsulate=True):
...@@ -355,7 +356,7 @@ class CapaModule(XModule): ...@@ -355,7 +356,7 @@ class CapaModule(XModule):
id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "</div>" id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + "</div>"
# now do the substitutions which are filesystem based, e.g. '/static/' prefixes # now do the substitutions which are filesystem based, e.g. '/static/' prefixes
return self.system.replace_urls(html, self.metadata['data_dir'], course_namespace=self.location) return self.system.replace_urls(html)
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
''' '''
...@@ -460,7 +461,7 @@ class CapaModule(XModule): ...@@ -460,7 +461,7 @@ class CapaModule(XModule):
new_answers = dict() new_answers = dict()
for answer_id in answers: for answer_id in answers:
try: try:
new_answer = {answer_id: self.system.replace_urls(answers[answer_id], self.metadata['data_dir'], course_namespace=self.location)} new_answer = {answer_id: self.system.replace_urls(answers[answer_id])}
except TypeError: except TypeError:
log.debug('Unable to perform URL substitution on answers[%s]: %s' % (answer_id, answers[answer_id])) log.debug('Unable to perform URL substitution on answers[%s]: %s' % (answer_id, answers[answer_id]))
new_answer = {answer_id: answers[answer_id]} new_answer = {answer_id: answers[answer_id]}
...@@ -668,18 +669,18 @@ class CapaDescriptor(RawDescriptor): ...@@ -668,18 +669,18 @@ class CapaDescriptor(RawDescriptor):
# TODO (vshnayder): do problems have any other metadata? Do they # TODO (vshnayder): do problems have any other metadata? Do they
# actually use type and points? # actually use type and points?
metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points') metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
def get_context(self): def get_context(self):
_context = RawDescriptor.get_context(self) _context = RawDescriptor.get_context(self)
_context.update({'markdown': self.metadata.get('markdown', '')}) _context.update({'markdown': self.metadata.get('markdown', '')})
return _context return _context
@property @property
def editable_metadata_fields(self): def editable_metadata_fields(self):
"""Remove metadata from the editable fields since it has its own editor""" """Remove metadata from the editable fields since it has its own editor"""
subset = super(CapaDescriptor,self).editable_metadata_fields subset = super(CapaDescriptor,self).editable_metadata_fields
if 'markdown' in subset: if 'markdown' in subset:
subset.remove('markdown') subset.remove('markdown')
return subset return subset
......
...@@ -17,4 +17,23 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d ...@@ -17,4 +17,23 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
# export the static assets # export the static assets
contentstore.export_all_for_course(course_location, root_dir + '/' + course_dir + '/static/') contentstore.export_all_for_course(course_location, root_dir + '/' + course_dir + '/static/')
# export the static tabs
export_extra_content(export_fs, modulestore, course_location, 'static_tab', 'tabs', '.html')
# export the custom tags
export_extra_content(export_fs, modulestore, course_location, 'custom_tag_template', 'custom_tags')
def export_extra_content(export_fs, modulestore, course_location, category_type, dirname, file_suffix = ''):
query_loc = Location('i4x', course_location.org, course_location.course, category_type, None)
items = modulestore.get_items(query_loc)
if len(items) > 0:
item_dir = export_fs.makeopendir(dirname)
for item in items:
with item_dir.open(item.location.name + file_suffix, 'w') as item_file:
item_file.write(item.definition['data'].encode('utf8'))
\ No newline at end of file
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
{"type": "courseware"}, {"type": "courseware"},
{"type": "course_info", "name": "Course Info"}, {"type": "course_info", "name": "Course Info"},
{"type": "static_tab", "url_slug": "syllabus", "name": "Syllabus"}, {"type": "static_tab", "url_slug": "syllabus", "name": "Syllabus"},
{"type": "static_tab", "url_slug": "resources", "name": "Resources"},
{"type": "discussion", "name": "Discussion"}, {"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"}, {"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"} {"type": "progress", "name": "Progress"}
......
...@@ -83,13 +83,12 @@ def get_opt_course_with_access(user, course_id, action): ...@@ -83,13 +83,12 @@ def get_opt_course_with_access(user, course_id, action):
return None return None
return get_course_with_access(user, course_id, action) return get_course_with_access(user, course_id, action)
def course_image_url(course): def course_image_url(course):
"""Try to look up the image url for the course. If it's not found, """Try to look up the image url for the course. If it's not found,
log an error and return the dead link""" log an error and return the dead link"""
if isinstance(modulestore(), XMLModuleStore): if isinstance(modulestore(), XMLModuleStore):
path = course.metadata['data_dir'] + "/images/course_image.jpg" return '/static/' + course.metadata['data_dir'] + "/images/course_image.jpg"
return try_staticfiles_lookup(path)
else: else:
loc = course.location._replace(tag='c4x', category='asset', name='images_course_image.jpg') loc = course.location._replace(tag='c4x', category='asset', name='images_course_image.jpg')
path = StaticContent.get_url_path_from_location(loc) path = StaticContent.get_url_path_from_location(loc)
......
...@@ -3,6 +3,8 @@ import logging ...@@ -3,6 +3,8 @@ import logging
import pyparsing import pyparsing
import sys import sys
from functools import partial
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -244,7 +246,11 @@ def _get_module(user, request, descriptor, student_module_cache, course_id, ...@@ -244,7 +246,11 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
# TODO (cpennington): This should be removed when all html from # TODO (cpennington): This should be removed when all html from
# a module is coming through get_html and is therefore covered # a module is coming through get_html and is therefore covered
# by the replace_static_urls code below # by the replace_static_urls code below
replace_urls=replace_urls, replace_urls=partial(
replace_urls,
staticfiles_prefix='/static/' + descriptor.metadata.get('data_dir', ''),
course_namespace=descriptor.location._replace(category=None, name=None),
),
node_path=settings.NODE_PATH, node_path=settings.NODE_PATH,
anonymous_student_id=unique_id_for_user(user), anonymous_student_id=unique_id_for_user(user),
course_id=course_id, course_id=course_id,
...@@ -280,7 +286,7 @@ def _get_module(user, request, descriptor, student_module_cache, course_id, ...@@ -280,7 +286,7 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
module.get_html = replace_static_urls( module.get_html = replace_static_urls(
_get_html, _get_html,
module.metadata['data_dir'] if 'data_dir' in module.metadata else '', '/static/' + module.metadata.get('data_dir', ''),
course_namespace = module.location._replace(category=None, name=None)) course_namespace = module.location._replace(category=None, name=None))
# Allow URLs of the form '/course/' refer to the root of multicourse directory # Allow URLs of the form '/course/' refer to the root of multicourse directory
......
...@@ -233,10 +233,13 @@ def index(request, course_id, chapter=None, section=None, ...@@ -233,10 +233,13 @@ def index(request, course_id, chapter=None, section=None,
# Specifically asked-for section doesn't exist # Specifically asked-for section doesn't exist
raise Http404 raise Http404
# Load all descendents of the section, because we're going to display it's # Load all descendants of the section, because we're going to display its
# html, which in general will need all of its children # html, which in general will need all of its children
section_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course.id, request.user, section_descriptor, depth=None)
section_module = get_module(request.user, request, section_descriptor.location, section_module = get_module(request.user, request, section_descriptor.location,
student_module_cache, course.id, position=position, depth=None) section_module_cache, course.id, position=position, depth=None)
if section_module is None: if section_module is None:
# User may be trying to be clever and access something # User may be trying to be clever and access something
# they don't have access to. # they don't have access to.
......
...@@ -266,24 +266,6 @@ STATICFILES_DIRS = [ ...@@ -266,24 +266,6 @@ STATICFILES_DIRS = [
COMMON_ROOT / "static", COMMON_ROOT / "static",
PROJECT_ROOT / "static", PROJECT_ROOT / "static",
] ]
if os.path.isdir(DATA_DIR):
# Add the full course repo if there is no static directory
STATICFILES_DIRS += [
# TODO (cpennington): When courses are stored in a database, this
# should no longer be added to STATICFILES
(course_dir, DATA_DIR / course_dir)
for course_dir in os.listdir(DATA_DIR)
if (os.path.isdir(DATA_DIR / course_dir) and
not os.path.isdir(DATA_DIR / course_dir / 'static'))
]
# Otherwise, add only the static directory from the course dir
STATICFILES_DIRS += [
# TODO (cpennington): When courses are stored in a database, this
# should no longer be added to STATICFILES
(course_dir, DATA_DIR / course_dir / 'static')
for course_dir in os.listdir(DATA_DIR)
if (os.path.isdir(DATA_DIR / course_dir / 'static'))
]
# Locale/Internationalization # Locale/Internationalization
TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
...@@ -468,7 +450,7 @@ PIPELINE_JS = { ...@@ -468,7 +450,7 @@ PIPELINE_JS = {
'source_filenames': sorted( 'source_filenames': sorted(
set(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/**/*.coffee') + set(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/**/*.coffee') +
rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/**/*.coffee')) - rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/**/*.coffee')) -
set(courseware_js + discussion_js + staff_grading_js + peer_grading_js) set(courseware_js + discussion_js + staff_grading_js + peer_grading_js)
) + [ ) + [
'js/form.ext.js', 'js/form.ext.js',
'js/my_courses_dropdown.js', 'js/my_courses_dropdown.js',
......
...@@ -106,6 +106,27 @@ VIRTUAL_UNIVERSITIES = [] ...@@ -106,6 +106,27 @@ VIRTUAL_UNIVERSITIES = []
COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE" COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE"
############################## Course static files ##########################
if os.path.isdir(DATA_DIR):
# Add the full course repo if there is no static directory
STATICFILES_DIRS += [
# TODO (cpennington): When courses are stored in a database, this
# should no longer be added to STATICFILES
(course_dir, DATA_DIR / course_dir)
for course_dir in os.listdir(DATA_DIR)
if (os.path.isdir(DATA_DIR / course_dir) and
not os.path.isdir(DATA_DIR / course_dir / 'static'))
]
# Otherwise, add only the static directory from the course dir
STATICFILES_DIRS += [
# TODO (cpennington): When courses are stored in a database, this
# should no longer be added to STATICFILES
(course_dir, DATA_DIR / course_dir / 'static')
for course_dir in os.listdir(DATA_DIR)
if (os.path.isdir(DATA_DIR / course_dir / 'static'))
]
################################# mitx revision string ##################### ################################# mitx revision string #####################
MITX_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip() MITX_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip()
......
<section id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}"></section> <section id="problem_${element_id}" class="problems-wrapper" data-problem-id="${id}" data-url="${ajax_url}" progress="${progress}"></section>
...@@ -39,7 +39,14 @@ ...@@ -39,7 +39,14 @@
</article> </article>
<article class="response"> <article class="response">
<h3>Will certificates be awarded?</h3> <h3>Will certificates be awarded?</h3>
<p>Yes. Online learners who demonstrate mastery of subjects can earn a certificate of mastery. Certificates will be issued by edX under the name of the underlying "X University" from where the course originated, i.e. HarvardX, <em>MITx</em> or BerkeleyX. For the courses in Fall 2012, those certificates will be free. There is a plan to charge a modest fee for certificates in the future.</p> <p>Yes. Online learners who demonstrate mastery of subjects can earn a certificate
of mastery. Certificates will be issued at the discretion of edX and the underlying
X University that offered the course under the name of the underlying "X
University" from where the course originated, i.e. HarvardX, MITx or BerkeleyX.
For the courses in Fall 2012, those certificates will be free. There is a plan to
charge a modest fee for certificates in the future. Note: At this time, edX is
holding certificates for learners connected with Cuba, Iran, Syria and Sudan
pending confirmation that the issuance is in compliance with U.S. embargoes.</p>
</article> </article>
<article class="response"> <article class="response">
<h3>What will the scope of the online courses be? How many? Which faculty?</h3> <h3>What will the scope of the online courses be? How many? Which faculty?</h3>
......
...@@ -180,8 +180,17 @@ ...@@ -180,8 +180,17 @@
<article class="response"> <article class="response">
<h3 class="question">Will I get a certificate for taking an edX course?</h3> <h3 class="question">Will I get a certificate for taking an edX course?</h3>
<div class="answer" id="certificates_and_credits_faq_answer_0"> <div class ="answer" id="certificates_and_credits_faq_answer_0">
<p>Online learners who receive a passing grade for a course will receive a certificate of mastery from edX and the underlying X University that offered the course. For example, a certificate of mastery for MITx’s 6.002x Circuits & Electronics will come from edX and MITx.</p> <p>Online learners who receive a passing grade for a course will receive a certificate
of mastery at the discretion of edX and the underlying X University that offered
the course. For example, a certificate of mastery for MITx’s 6.002x Circuits &amp;
Electronics will come from edX and MITx.</p>
<p>If you passed the course, your certificate of mastery will be delivered online
through edx.org. So be sure to check your email in the weeks following the final
grading – you will be able to download and print your certificate. Note: At this
time, edX is holding certificates for learners connected with Cuba, Iran, Syria
and Sudan pending confirmation that the issuance is in compliance with U.S.
embargoes.</p>
</div> </div>
</article> </article>
<article class="response"> <article class="response">
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<h2>Pilot project offers online courses, educational support and jobs training through Boston community centers</h2> <h2>Pilot project offers online courses, educational support and jobs training through Boston community centers</h2>
<p><strong>CAMBRIDGE, MA &ndash; January 29, 2013 &ndash;</strong> <p><strong>CAMBRIDGE, MA &ndash; January 29, 2013 &ndash;</strong>
<a href="http://www.edx.org">EdX</a>, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), announced today a pilot project with the City of Boston, Harvard and MIT to make online courses available through internet-connected Boston neighborhood community centers, high schools and libraries. A first-of-its-kind project, BostonX brings together innovators from the country’s center of higher education to offer Boston residents access to courses, internships, job training and placement services, and locations for edX students to gather, socialize and deepen learning.</p> <a href="http://www.edx.org">EdX</a>, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), announced today a pilot project with the City of Boston, Harvard and MIT to make online courses available through internet-connected Boston neighborhood community centers, high schools and libraries. A first-of-its-kind project, BostonX brings together innovators from the country’s center of higher education to offer Boston residents access to courses, internships, job training and placement services, and locations for edX students to gather, socialize and deepen learning.</p>
<p>“We must connect adults and youth in our neighborhoods with the opportunities of the knowledge economy,” said Mayor Tom Menino. “BostonX will help update our neighbors’ skills and our community centers. As a first step, I’m pleased to announce a pilot with Harvard, MIT and edX, their online learning initiative, which will bring free courses and training to our community centers.”</p> <p>“We must connect adults and youth in our neighborhoods with the opportunities of the knowledge economy,” said Mayor Tom Menino. “BostonX will help update our neighbors’ skills and our community centers. As a first step, I’m pleased to announce a pilot with Harvard, MIT and edX, their online learning initiative, which will bring free courses and training to our community centers.”</p>
......
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