From 28a2dd9a18e1aafe14d26995ad0cfcacb83f6d6e Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Mon, 28 Jan 2013 16:33:28 -0500
Subject: [PATCH 01/23] support reordering of static tabs in studio
---
cms/djangoapps/contentstore/views.py | 41 +++++++++++++++++++++++--
cms/static/coffee/src/views/tabs.coffee | 16 +++++++++-
cms/urls.py | 1 +
3 files changed, 55 insertions(+), 3 deletions(-)
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 816ccab091..326b47ee64 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -903,6 +903,36 @@ def static_pages(request, org, course, coursename):
def edit_static(request, org, course, coursename):
return render_to_response('edit-static-page.html', {})
+
+@login_required
+@expect_json
+def reorder_tabs(request):
+ tabs = request.POST['tabs']
+ logging.debug('tabs = {0} {1}'.format(tabs.__class__, tabs))
+
+ if len(tabs) > 0:
+ course = get_course_for_item(tabs[0])
+
+ if not has_access(request.user, course.location):
+ raise PermissionDenied()
+
+ # first filter out all non-static tabs from the tabs list
+ course_tabs = [t for t in course.tabs if t['type'] != 'static_tab']
+
+ # OK, re-assemble the static tabs in the new order
+ for tab in tabs:
+ item = modulestore('direct').get_item(Location(tab))
+
+ course_tabs.append({ 'type':'static_tab',
+ 'name' : item.metadata.get('display_name'),
+ 'url_slug' : item.location.name}
+ )
+
+ course.tabs = course_tabs
+ modulestore('direct').update_metadata(course.location, course.metadata)
+
+ return HttpResponse()
+
@login_required
@ensure_csrf_cookie
def edit_tabs(request, org, course, coursename):
@@ -914,12 +944,19 @@ def edit_tabs(request, org, course, coursename):
if not has_access(request.user, location):
raise PermissionDenied()
- static_tabs = modulestore('direct').get_items(static_tabs_loc)
-
# see tabs have been uninitialized (e.g. supporing courses created before tab support in studio)
if course_item.tabs is None or len(course_item.tabs) == 0:
initialize_course_tabs(course_item)
+ # first get all static tabs from the tabs list
+ # we do this because this is also the order in which items are displayed in the LMS
+ static_tabs_refs = [t for t in course_item.tabs if t['type'] == 'static_tab']
+
+ static_tabs = []
+ for static_tab_ref in static_tabs_refs:
+ static_tab_loc = Location(location)._replace(category='static_tab', name=static_tab_ref['url_slug'])
+ static_tabs.append(modulestore('direct').get_item(static_tab_loc))
+
components = [
static_tab.location.url()
for static_tab
diff --git a/cms/static/coffee/src/views/tabs.coffee b/cms/static/coffee/src/views/tabs.coffee
index 1fbc6ffa7f..3799eb25ee 100644
--- a/cms/static/coffee/src/views/tabs.coffee
+++ b/cms/static/coffee/src/views/tabs.coffee
@@ -15,7 +15,7 @@ class CMS.Views.TabsEdit extends Backbone.View
@$('.components').sortable(
handle: '.drag-handle'
- update: (event, ui) => alert 'not yet implemented!'
+ update: @tabMoved
helper: 'clone'
opacity: '0.5'
placeholder: 'component-placeholder'
@@ -24,6 +24,20 @@ class CMS.Views.TabsEdit extends Backbone.View
items: '> .component'
)
+ tabMoved: (event, ui) =>
+ tabs = []
+ @$('.component').each((idx, element) =>
+ tabs.push($(element).data('id'))
+ )
+ $.ajax({
+ type:'POST',
+ url: '/reorder_tabs',
+ data: JSON.stringify({
+ tabs : tabs
+ }),
+ contentType: 'application/json'
+ })
+
addNewTab: (event) =>
event.preventDefault()
diff --git a/cms/urls.py b/cms/urls.py
index 6f8736551b..2b329dc16b 100644
--- a/cms/urls.py
+++ b/cms/urls.py
@@ -17,6 +17,7 @@ urlpatterns = ('',
url(r'^publish_draft$', 'contentstore.views.publish_draft', name='publish_draft'),
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'^reorder_tabs', 'contentstore.views.reorder_tabs', name='reorder_tabs'),
url(r'^(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$',
'contentstore.views.course_index', name='course_index'),
From 855c8bb7e7c7f9a6f1258d3e13e0f7a1cc6d8037 Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Tue, 29 Jan 2013 13:44:42 -0500
Subject: [PATCH 02/23] add unit test for tab reordering
---
cms/djangoapps/contentstore/tests/tests.py | 28 +++++++++++++++++++
cms/djangoapps/contentstore/views.py | 1 -
.../data/full/policies/6.002_Spring_2012.json | 1 +
3 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 3025ee78a4..bfdcfe1438 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -350,6 +350,34 @@ class ContentStoreTest(TestCase):
def test_edit_unit_full(self):
self.check_edit_unit('full')
+ def test_static_tab_reordering(self):
+ import_from_xml(modulestore(), 'common/test/data/', ['full'])
+
+ # reverse the ordering
+ tabs = { 'tabs' : ['i4x://edX/full/static_tab/resources', 'i4x://edX/full/static_tab/syllabus'] }
+ resp = self.client.post(reverse('reorder_tabs'), json.dumps(tabs), "application/json")
+
+ ms = modulestore('direct')
+ 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
+
+ resource_idx = 0
+ syllabus_idx = 0
+ idx = 0
+ for tab in course.tabs:
+ if tab['type'] == 'static_tab':
+ if tab['url_slug'] == 'resources':
+ resource_idx = idx
+ elif tab['url_slug'] == 'syllabus':
+ syllabus_idx = idx
+ idx+=1
+
+ self.assertLess(resource_idx, syllabus_idx)
+
+
+
+
+
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
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 326b47ee64..205bf787fd 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -908,7 +908,6 @@ def edit_static(request, org, course, coursename):
@expect_json
def reorder_tabs(request):
tabs = request.POST['tabs']
- logging.debug('tabs = {0} {1}'.format(tabs.__class__, tabs))
if len(tabs) > 0:
course = get_course_for_item(tabs[0])
diff --git a/common/test/data/full/policies/6.002_Spring_2012.json b/common/test/data/full/policies/6.002_Spring_2012.json
index 345309ff5c..2f55528b7b 100644
--- a/common/test/data/full/policies/6.002_Spring_2012.json
+++ b/common/test/data/full/policies/6.002_Spring_2012.json
@@ -8,6 +8,7 @@
{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "static_tab", "url_slug": "syllabus", "name": "Syllabus"},
+ {"type": "static_tab", "url_slug": "resources", "name": "Resources"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}
From 26cb1c5a2564ec85d3dea8f762c559397fe001b5 Mon Sep 17 00:00:00 2001
From: cahrens
Date: Tue, 29 Jan 2013 16:30:41 -0500
Subject: [PATCH 03/23] Fixes for
https://edx.lighthouseapp.com/projects/102637/tickets/144 (progress tab not
updating).
---
common/lib/xmodule/xmodule/capa_module.py | 1 +
lms/djangoapps/courseware/views.py | 7 +++++--
lms/templates/problem_ajax.html | 2 +-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index e4ab804f49..f33da6e3a4 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -229,6 +229,7 @@ class CapaModule(XModule):
'element_id': self.location.html_id(),
'id': self.id,
'ajax_url': self.system.ajax_url,
+ 'progress': Progress.to_js_status_str(self.get_progress())
})
def get_problem_html(self, encapsulate=True):
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 760ccb1d05..8c529a8585 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -233,10 +233,13 @@ def index(request, course_id, chapter=None, section=None,
# Specifically asked-for section doesn't exist
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 it's
# 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,
- student_module_cache, course.id, position=position, depth=None)
+ section_module_cache, course.id, position=position, depth=None)
if section_module is None:
# User may be trying to be clever and access something
# they don't have access to.
diff --git a/lms/templates/problem_ajax.html b/lms/templates/problem_ajax.html
index 012e4276c3..42cd18c4e3 100644
--- a/lms/templates/problem_ajax.html
+++ b/lms/templates/problem_ajax.html
@@ -1 +1 @@
-
+
From b8c1dba1cba892a863bb84b4249b193a1711cbe1 Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Tue, 29 Jan 2013 17:31:12 -0500
Subject: [PATCH 04/23] Catch save error and throw it in the user's face (bug
147). May need to add catches in more places or put in $.ajaxSetup?
---
cms/static/js/views/settings/main_settings_view.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js
index 9037d4510c..704623b56e 100644
--- a/cms/static/js/views/settings/main_settings_view.js
+++ b/cms/static/js/views/settings/main_settings_view.js
@@ -55,7 +55,10 @@ CMS.Views.ValidatingView = Backbone.View.extend({
var newVal = $(event.currentTarget).val();
if (currentVal != newVal) {
this.clearValidationErrors();
- this.model.save(field, newVal);
+ this.model.save(field, newVal, { error : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ if (error.responseText) window.alert("Error: " + error.responseText);
+ }});
return true;
}
else return false;
From d132f2e18a628caf972e58b4e6be1bd1ab93e3ee Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 28 Jan 2013 15:05:04 -0500
Subject: [PATCH 05/23] Only put data repos in STATICFILES_DIRS if we're
serving them from django (which is only in dev)
---
lms/envs/common.py | 20 +-------------------
lms/envs/dev.py | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+), 19 deletions(-)
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 16472795e0..a2d9614109 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -266,24 +266,6 @@ STATICFILES_DIRS = [
COMMON_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
TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
@@ -468,7 +450,7 @@ PIPELINE_JS = {
'source_filenames': sorted(
set(rooted_glob(COMMON_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/my_courses_dropdown.js',
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index 99ee9662ee..338a31f641 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -106,6 +106,27 @@ VIRTUAL_UNIVERSITIES = []
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_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip()
From 89f984c08be3747e27e2ea3a2345839c6ee2c24e Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Tue, 29 Jan 2013 13:48:07 -0500
Subject: [PATCH 06/23] Prefix courseware urls with /static, since they are no
longer served through collectstatic
---
cms/djangoapps/contentstore/views.py | 2 +-
lms/djangoapps/courseware/module_render.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 816ccab091..cc26510534 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -527,7 +527,7 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
module.get_html = replace_static_urls(
module.get_html,
- module.metadata.get('data_dir', module.location.course),
+ '/static/' + module.metadata.get('data_dir', module.location.course),
course_namespace = Location([module.location.tag, module.location.org, module.location.course, None, None])
)
save_preview_state(request, preview_id, descriptor.location.url(),
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 7ed32c8597..a641f6e94c 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -280,7 +280,7 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
module.get_html = replace_static_urls(
_get_html,
- module.metadata['data_dir'] if 'data_dir' in module.metadata else '',
+ '/static/' + module.metadata['data_dir'] if 'data_dir' in module.metadata else '',
course_namespace = module.location._replace(category=None, name=None))
# Allow URLs of the form '/course/' refer to the root of multicourse directory
From 95f2c9e275108f7045df67c33e45b88a9b37718e Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Tue, 29 Jan 2013 14:20:01 -0500
Subject: [PATCH 07/23] Standardize how static urls are replaced by modules in
their own html
---
common/lib/xmodule/xmodule/capa_module.py | 4 ++--
lms/djangoapps/courseware/module_render.py | 8 ++++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index e4ab804f49..688737d883 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -355,7 +355,7 @@ class CapaModule(XModule):
id=self.location.html_id(), ajax_url=self.system.ajax_url) + html + ""
# 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):
'''
@@ -460,7 +460,7 @@ class CapaModule(XModule):
new_answers = dict()
for answer_id in answers:
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:
log.debug('Unable to perform URL substitution on answers[%s]: %s' % (answer_id, answers[answer_id]))
new_answer = {answer_id: answers[answer_id]}
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index a641f6e94c..23dbc8c8a6 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -244,7 +244,11 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
# TODO (cpennington): This should be removed when all html from
# a module is coming through get_html and is therefore covered
# by the replace_static_urls code below
- replace_urls=replace_urls,
+ replace_urls=partial(
+ replace_urls,
+ staticfiles_prefix='/static/' + module.metadata.get('data_dir', ''),
+ course_namespace=module.location._replace(category=None, name=None),
+ ),
node_path=settings.NODE_PATH,
anonymous_student_id=unique_id_for_user(user),
course_id=course_id,
@@ -280,7 +284,7 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
module.get_html = replace_static_urls(
_get_html,
- '/static/' + 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))
# Allow URLs of the form '/course/' refer to the root of multicourse directory
From 9ae83338c627af75ed18d9894be265ba9a82c5b6 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Tue, 29 Jan 2013 14:20:21 -0500
Subject: [PATCH 08/23] Whitespace cleanup
---
cms/djangoapps/contentstore/views.py | 136 +++++++++++-----------
common/lib/xmodule/xmodule/capa_module.py | 6 +-
2 files changed, 71 insertions(+), 71 deletions(-)
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index cc26510534..9331e97fec 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -132,7 +132,7 @@ def has_access(user, location, role=STAFF_ROLE_NAME):
Return True if user allowed to access this piece of data
Note that the CMS permissions model is with respect to courses
There is a super-admin permissions if user.is_staff is set
- Also, since we're unifying the user database between LMS and CAS,
+ Also, since we're unifying the user database between LMS and CAS,
I'm presuming that the course instructor (formally known as admin)
will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our queries here as INSTRUCTOR
has all the rights that STAFF do
@@ -154,7 +154,7 @@ def course_index(request, org, course, name):
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
@@ -213,7 +213,7 @@ def edit_subsection(request, location):
# remove all metadata from the generic dictionary that is presented in a more normalized UI
- policy_metadata = dict((key,value) for key, value in item.metadata.iteritems()
+ policy_metadata = dict((key,value) for key, value in item.metadata.iteritems()
if key not in ['display_name', 'start', 'due', 'format'] and key not in item.system_metadata_fields)
can_view_live = False
@@ -292,7 +292,7 @@ def edit_unit(request, location):
containing_section = modulestore().get_item(containing_section_locs[0])
# cdodge hack. We're having trouble previewing drafts via jump_to redirect
- # so let's generate the link url here
+ # so let's generate the link url here
# need to figure out where this item is in the list of children as the preview will need this
index =1
@@ -303,12 +303,12 @@ def edit_unit(request, location):
preview_lms_link = '//{preview}{lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}'.format(
preview='preview.',
- lms_base=settings.LMS_BASE,
+ lms_base=settings.LMS_BASE,
org=course.location.org,
- course=course.location.course,
- course_name=course.location.name,
- section=containing_section.location.name,
- subsection=containing_subsection.location.name,
+ course=course.location.course,
+ course_name=course.location.name,
+ section=containing_section.location.name,
+ subsection=containing_subsection.location.name,
index=index)
unit_state = compute_unit_state(item)
@@ -359,14 +359,14 @@ def assignment_type_update(request, org, course, category, name):
location = Location(['i4x', org, course, category, name])
if not has_access(request.user, location):
raise HttpResponseForbidden()
-
+
if request.method == 'GET':
- return HttpResponse(json.dumps(CourseGradingModel.get_section_grader_type(location)),
+ return HttpResponse(json.dumps(CourseGradingModel.get_section_grader_type(location)),
mimetype="application/json")
elif request.method == 'POST': # post or put, doesn't matter.
- return HttpResponse(json.dumps(CourseGradingModel.update_section_grader_type(location, request.POST)),
+ return HttpResponse(json.dumps(CourseGradingModel.update_section_grader_type(location, request.POST)),
mimetype="application/json")
-
+
def user_author_string(user):
'''Get an author string for commits by this user. Format:
@@ -511,20 +511,20 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
error_msg=exc_info_to_str(sys.exc_info())
).xmodule_constructor(system)(None, None)
- # cdodge: Special case
+ # cdodge: Special case
if module.location.category == 'static_tab':
module.get_html = wrap_xmodule(
module.get_html,
module,
"xmodule_tab_display.html",
)
- else:
+ else:
module.get_html = wrap_xmodule(
module.get_html,
module,
"xmodule_display.html",
)
-
+
module.get_html = replace_static_urls(
module.get_html,
'/static/' + module.metadata.get('data_dir', module.location.course),
@@ -555,7 +555,7 @@ def _xmodule_recurse(item, action):
_xmodule_recurse(child, action)
action(item)
-
+
@login_required
@expect_json
@@ -590,7 +590,7 @@ def delete_item(request):
# delete_item on a vertical tries to delete the draft version leaving the
# requested delete to never occur
if item.location.revision is None and item.location.category=='vertical' and delete_all_versions:
- modulestore('direct').delete_item(item.location)
+ modulestore('direct').delete_item(item.location)
return HttpResponse()
@@ -609,7 +609,7 @@ def save_item(request):
if request.POST.get('data') is not None:
data = request.POST['data']
store.update_item(item_location, data)
-
+
# cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection
@@ -699,7 +699,7 @@ def unpublish_unit(request):
def clone_item(request):
parent_location = Location(request.POST['parent_location'])
template = Location(request.POST['template'])
-
+
display_name = request.POST.get('display_name')
if not has_access(request.user, parent_location):
@@ -739,9 +739,9 @@ def upload_asset(request, org, course, coursename):
location = ['i4x', org, course, 'course', coursename]
if not has_access(request.user, location):
return HttpResponseForbidden()
-
+
# Does the course actually exist?!? Get anything from it to prove its existance
-
+
try:
item = modulestore().get_item(location)
except:
@@ -775,9 +775,9 @@ def upload_asset(request, org, course, coursename):
# readback the saved content - we need the database timestamp
readback = contentstore().find(content.location)
-
- response_payload = {'displayname' : content.name,
- 'uploadDate' : get_date_display(readback.last_modified_at),
+
+ response_payload = {'displayname' : content.name,
+ 'uploadDate' : get_date_display(readback.last_modified_at),
'url' : StaticContent.get_url_path_from_location(content.location),
'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
'msg' : 'Upload completed'
@@ -793,7 +793,7 @@ This view will return all CMS users who are editors for the specified course
@login_required
@ensure_csrf_cookie
def manage_users(request, location):
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=STAFF_ROLE_NAME):
raise PermissionDenied()
@@ -809,7 +809,7 @@ def manage_users(request, location):
'allow_actions' : has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME),
'request_user_id' : request.user.id
})
-
+
def create_json_response(errmsg = None):
if errmsg is not None:
@@ -831,13 +831,13 @@ def add_user(request, location):
if email=='':
return create_json_response('Please specify an email address.')
-
+
# check that logged in user has admin permissions to this course
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
raise PermissionDenied()
-
+
user = get_user_by_email(email)
-
+
# user doesn't exist?!? Return error.
if user is None:
return create_json_response('Could not find user by email address \'{0}\'.'.format(email))
@@ -860,7 +860,7 @@ the specified course
@ensure_csrf_cookie
def remove_user(request, location):
email = request.POST["email"]
-
+
# check that logged in user has admin permissions on this course
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
raise PermissionDenied()
@@ -887,7 +887,7 @@ def landing(request, org, course, coursename):
def static_pages(request, org, course, coursename):
location = ['i4x', org, course, 'course', coursename]
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
@@ -906,7 +906,7 @@ def edit_static(request, org, course, coursename):
@login_required
@ensure_csrf_cookie
def edit_tabs(request, org, course, coursename):
- location = ['i4x', org, course, 'course', coursename]
+ location = ['i4x', org, course, 'course', coursename]
course_item = modulestore().get_item(location)
static_tabs_loc = Location('i4x', org, course, 'static_tab', None)
@@ -928,7 +928,7 @@ def edit_tabs(request, org, course, coursename):
return render_to_response('edit-tabs.html', {
'active_tab': 'pages',
- 'context_course':course_item,
+ 'context_course':course_item,
'components': components
})
@@ -949,13 +949,13 @@ def course_info(request, org, course, name, provided_id=None):
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
-
+
course_module = modulestore().get_item(location)
-
+
# get current updates
location = ['i4x', org, course, 'course_info', "updates"]
@@ -966,7 +966,7 @@ def course_info(request, org, course, name, provided_id=None):
'course_updates' : json.dumps(get_course_updates(location)),
'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url()
})
-
+
@expect_json
@login_required
@ensure_csrf_cookie
@@ -980,7 +980,7 @@ def course_info_updates(request, org, course, provided_id=None):
# ??? No way to check for access permission afaik
# get current updates
location = ['i4x', org, course, 'course_info', "updates"]
-
+
# Hmmm, provided_id is coming as empty string on create whereas I believe it used to be None :-(
# Possibly due to my removing the seemingly redundant pattern in urls.py
if provided_id == '':
@@ -995,7 +995,7 @@ def course_info_updates(request, org, course, provided_id=None):
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
-
+
if request.method == 'GET':
return HttpResponse(json.dumps(get_course_updates(location)), mimetype="application/json")
elif real_method == 'DELETE': # coming as POST need to pull from Request Header X-HTTP-Method-Override DELETE
@@ -1012,7 +1012,7 @@ def course_info_updates(request, org, course, provided_id=None):
@ensure_csrf_cookie
def module_info(request, module_location):
location = Location(module_location)
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
@@ -1025,10 +1025,10 @@ def module_info(request, module_location):
rewrite_static_links = request.GET.get('rewrite_url_links','True') in ['True', 'true']
logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links','False'), rewrite_static_links))
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
- raise PermissionDenied()
+ raise PermissionDenied()
if real_method == 'GET':
return HttpResponse(json.dumps(get_module_info(get_modulestore(location), location, rewrite_static_links=rewrite_static_links)), mimetype="application/json")
@@ -1046,20 +1046,20 @@ def get_course_settings(request, org, course, name):
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
-
+
course_module = modulestore().get_item(location)
course_details = CourseDetails.fetch(location)
-
+
return render_to_response('settings.html', {
- 'active_tab': 'settings',
+ 'active_tab': 'settings',
'context_course': course_module,
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
})
-
+
@expect_json
@login_required
@ensure_csrf_cookie
@@ -1082,13 +1082,13 @@ def course_settings_updates(request, org, course, name, section):
elif section == 'grading':
manager = CourseGradingModel
else: return
-
+
if request.method == 'GET':
# Cannot just do a get w/o knowing the course name :-(
- return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course',name])), cls=CourseSettingsEncoder),
+ return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course',name])), cls=CourseSettingsEncoder),
mimetype="application/json")
elif request.method == 'POST': # post or put, doesn't matter.
- return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
+ return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
mimetype="application/json")
@expect_json
@@ -1101,7 +1101,7 @@ def course_grader_updates(request, org, course, name, grader_index=None):
org, course: Attributes of the Location for the item to edit
"""
-
+
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
@@ -1112,13 +1112,13 @@ def course_grader_updates(request, org, course, name, grader_index=None):
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
-
+
if real_method == 'GET':
# Cannot just do a get w/o knowing the course name :-(
- return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course',name]), grader_index)),
+ return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course',name]), grader_index)),
mimetype="application/json")
elif real_method == "DELETE":
- # ??? Shoudl this return anything? Perhaps success fail?
+ # ??? Shoudl this return anything? Perhaps success fail?
CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course',name]), grader_index)
return HttpResponse()
elif request.method == 'POST': # post or put, doesn't matter.
@@ -1135,7 +1135,7 @@ def asset_index(request, org, course, name):
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
-
+
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
@@ -1148,7 +1148,7 @@ def asset_index(request, org, course, name):
})
course_module = modulestore().get_item(location)
-
+
course_reference = StaticContent.compute_location(org, course, name)
assets = contentstore().get_all_content_for_course(course_reference)
@@ -1162,15 +1162,15 @@ def asset_index(request, org, course, name):
display_info = {}
display_info['displayname'] = asset['displayname']
display_info['uploadDate'] = get_date_display(asset['uploadDate'])
-
+
asset_location = StaticContent.compute_location(id['org'], id['course'], id['name'])
display_info['url'] = StaticContent.get_url_path_from_location(asset_location)
-
+
# note, due to the schema change we may not have a 'thumbnail_location' in the result set
_thumbnail_location = asset.get('thumbnail_location', None)
thumbnail_location = Location(_thumbnail_location) if _thumbnail_location is not None else None
display_info['thumb_url'] = StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None
-
+
asset_display.append(display_info)
return render_to_response('asset_index.html', {
@@ -1189,9 +1189,9 @@ def edge(request):
@expect_json
def create_new_course(request):
template = Location(request.POST['template'])
- org = request.POST.get('org')
- number = request.POST.get('number')
- display_name = request.POST.get('display_name')
+ org = request.POST.get('org')
+ number = request.POST.get('number')
+ display_name = request.POST.get('display_name')
try:
dest_location = Location('i4x', org, number, 'course', Location.clean(display_name))
@@ -1237,13 +1237,13 @@ def initialize_course_tabs(course):
# at least a list populated with the minimal times
# @TODO: I don't like the fact that the presentation tier is away of these data related constraints, let's find a better
# place for this. Also rather than using a simple list of dictionaries a nice class model would be helpful here
- course.tabs = [{"type": "courseware"},
- {"type": "course_info", "name": "Course Info"},
+ course.tabs = [{"type": "courseware"},
+ {"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}]
- modulestore('direct').update_metadata(course.location.url(), course.own_metadata)
+ modulestore('direct').update_metadata(course.location.url(), course.own_metadata)
@ensure_csrf_cookie
@login_required
@@ -1337,7 +1337,7 @@ def generate_export_course(request, org, course, name):
root_dir = path(mkdtemp())
# export out to a tempdir
-
+
logging.debug('root = {0}'.format(root_dir))
export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name)
@@ -1349,7 +1349,7 @@ def generate_export_course(request, org, course, name):
tf.close()
# remove temp dir
- shutil.rmtree(root_dir/name)
+ shutil.rmtree(root_dir/name)
wrapper = FileWrapper(export_file)
response = HttpResponse(wrapper, content_type='application/x-tgz')
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 688737d883..3947945beb 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -668,18 +668,18 @@ class CapaDescriptor(RawDescriptor):
# TODO (vshnayder): do problems have any other metadata? Do they
# actually use type and points?
metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
-
+
def get_context(self):
_context = RawDescriptor.get_context(self)
_context.update({'markdown': self.metadata.get('markdown', '')})
return _context
-
+
@property
def editable_metadata_fields(self):
"""Remove metadata from the editable fields since it has its own editor"""
subset = super(CapaDescriptor,self).editable_metadata_fields
if 'markdown' in subset:
- subset.remove('markdown')
+ subset.remove('markdown')
return subset
From e773b8e7b126718fbc8040148ef2455790f913c4 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Tue, 29 Jan 2013 14:22:39 -0500
Subject: [PATCH 09/23] Fix import error
---
lms/djangoapps/courseware/module_render.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 23dbc8c8a6..064fdf1210 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -3,6 +3,8 @@ import logging
import pyparsing
import sys
+from functools import partial
+
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
From 07487a8d8f5124d5cfaa16b58b488769f7c42ac2 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Tue, 29 Jan 2013 14:27:27 -0500
Subject: [PATCH 10/23] Use descriptor when setting up replace_urls, as it's
been created at that point
---
lms/djangoapps/courseware/module_render.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 064fdf1210..22d95ef8a2 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -248,8 +248,8 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
# by the replace_static_urls code below
replace_urls=partial(
replace_urls,
- staticfiles_prefix='/static/' + module.metadata.get('data_dir', ''),
- course_namespace=module.location._replace(category=None, name=None),
+ staticfiles_prefix='/static/' + descriptor.metadata.get('data_dir', ''),
+ course_namespace=descriptor.location._replace(category=None, name=None),
),
node_path=settings.NODE_PATH,
anonymous_student_id=unique_id_for_user(user),
From 2854d7d06695a9ead9920f90678cf3807c64644f Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Tue, 29 Jan 2013 16:21:46 -0500
Subject: [PATCH 11/23] Point course_image.jpg at the correct place in the
/static url space
---
lms/djangoapps/courseware/courses.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py
index 89a1496eca..03d5a89c64 100644
--- a/lms/djangoapps/courseware/courses.py
+++ b/lms/djangoapps/courseware/courses.py
@@ -83,13 +83,12 @@ def get_opt_course_with_access(user, course_id, action):
return None
return get_course_with_access(user, course_id, action)
-
+
def course_image_url(course):
"""Try to look up the image url for the course. If it's not found,
log an error and return the dead link"""
if isinstance(modulestore(), XMLModuleStore):
- path = course.metadata['data_dir'] + "/images/course_image.jpg"
- return try_staticfiles_lookup(path)
+ return '/static/' + course.metadata['data_dir'] + "/images/course_image.jpg"
else:
loc = course.location._replace(tag='c4x', category='asset', name='images_course_image.jpg')
path = StaticContent.get_url_path_from_location(loc)
From 3c1c61fb19ea96c1ed51e9b274c29cb5f0252c27 Mon Sep 17 00:00:00 2001
From: cahrens
Date: Wed, 30 Jan 2013 09:23:07 -0500
Subject: [PATCH 12/23] Fixes for
https://edx.lighthouseapp.com/projects/102637/tickets/144 (progress tab not
updating).
---
lms/djangoapps/courseware/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 8c529a8585..5d65d7c632 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -233,7 +233,7 @@ def index(request, course_id, chapter=None, section=None,
# Specifically asked-for section doesn't exist
raise Http404
- # Load all descendants 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
section_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course.id, request.user, section_descriptor, depth=None)
From ab4fd03dd8c5b42afcaa0258e27d51245d613671 Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Wed, 30 Jan 2013 09:43:27 -0500
Subject: [PATCH 13/23] Catch any and all save errors
---
.../js/models/settings/course_details.js | 12 +++++-
.../js/views/settings/main_settings_view.js | 38 +++++++++++++++----
2 files changed, 41 insertions(+), 9 deletions(-)
diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js
index ab80179142..61e31133fd 100644
--- a/cms/static/js/models/settings/course_details.js
+++ b/cms/static/js/models/settings/course_details.js
@@ -68,10 +68,18 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
save_videosource: function(newsource) {
// newsource either is or just the "speed:key, *" string
// 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 : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
// TODO remove all whitespace w/in string
else {
- if (this.get('intro_video') !== newsource) this.save('intro_video', newsource);
+ if (this.get('intro_video') !== newsource) this.save('intro_video', newsource,
+ { error : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
}
return this.videosourceSample();
diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js
index 704623b56e..602da60ed6 100644
--- a/cms/static/js/views/settings/main_settings_view.js
+++ b/cms/static/js/views/settings/main_settings_view.js
@@ -57,7 +57,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({
this.clearValidationErrors();
this.model.save(field, newVal, { error : function(model, error) {
// this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- if (error.responseText) window.alert("Error: " + error.responseText);
+ window.alert("Error during save: " + error.responseText);
}});
return true;
}
@@ -230,7 +230,11 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
time = 0;
}
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 : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
}
};
@@ -279,7 +283,11 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
},
removeSyllabus: function() {
- if (this.model.has('syllabus')) this.model.save({'syllabus': null});
+ if (this.model.has('syllabus')) this.model.save({'syllabus': null},
+ { error : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
},
assetSyllabus : function() {
@@ -312,7 +320,11 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
mirror.save();
cachethis.clearValidationErrors();
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 : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
}
});
}
@@ -407,7 +419,11 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
setGracePeriod : function(event) {
event.data.clearValidationErrors();
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 : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
},
updateModel : function(event) {
if (!this.selectorToField[event.currentTarget.id]) return;
@@ -543,7 +559,11 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
object[cutoff['designation']] = cutoff['cutoff'] / 100.0;
return object;
},
- {}));
+ {}),
+ { error : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
},
addNewGrade: function(e) {
@@ -674,7 +694,11 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
}
},
deleteModel : function(e) {
- this.model.destroy();
+ this.model.destroy(
+ { error : function(model, error) {
+ // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
+ window.alert("Error during save: " + error.responseText);
+ }});
e.preventDefault();
}
From 74fcf9661159b4d600d58b0281ff4bed9a076b75 Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Wed, 30 Jan 2013 09:43:57 -0500
Subject: [PATCH 14/23] don't hard code the ordering of the tabs in the test.
Take the current ordering defintion (in the test data) and reverse it
---
cms/djangoapps/contentstore/tests/tests.py | 27 +++++++++++-----------
1 file changed, 13 insertions(+), 14 deletions(-)
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index bfdcfe1438..fd811fb101 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -353,27 +353,26 @@ class ContentStoreTest(TestCase):
def test_static_tab_reordering(self):
import_from_xml(modulestore(), 'common/test/data/', ['full'])
- # reverse the ordering
- tabs = { 'tabs' : ['i4x://edX/full/static_tab/resources', 'i4x://edX/full/static_tab/syllabus'] }
- resp = self.client.post(reverse('reorder_tabs'), json.dumps(tabs), "application/json")
-
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_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
- resource_idx = 0
- syllabus_idx = 0
- idx = 0
+ course_tabs = []
for tab in course.tabs:
if tab['type'] == 'static_tab':
- if tab['url_slug'] == 'resources':
- resource_idx = idx
- elif tab['url_slug'] == 'syllabus':
- syllabus_idx = idx
- idx+=1
-
- self.assertLess(resource_idx, syllabus_idx)
+ course_tabs.append('i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
+ self.assertEqual(reverse_tabs, course_tabs)
From 99dd9636ced75a0af3fee003775591dcb9c91f2f Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Wed, 30 Jan 2013 10:43:36 -0500
Subject: [PATCH 15/23] fix copy+paste bug.
---
.../static_templates/press_releases/bostonx_announcement.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lms/templates/static_templates/press_releases/bostonx_announcement.html b/lms/templates/static_templates/press_releases/bostonx_announcement.html
index 88ca45b0a0..5aee02dd9e 100644
--- a/lms/templates/static_templates/press_releases/bostonx_announcement.html
+++ b/lms/templates/static_templates/press_releases/bostonx_announcement.html
@@ -21,7 +21,7 @@
Pilot project offers online courses, educational support and jobs training through Boston community centers
CAMBRIDGE, MA – January 29, 2013 –
-EdX, 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.
+EdX, 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.
“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.”
From 19927a83a5ac3301cc5a9570f7f864f0ec46afed Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Wed, 30 Jan 2013 11:24:55 -0500
Subject: [PATCH 16/23] Ensure settings and course info CRUD operations tell
the user if there's a server error and pull the server error handler into a
common function.
---
.../js/models/settings/course_details.js | 10 ++----
cms/static/js/views/course_info_edit.js | 16 ++++-----
cms/static/js/views/server_error.js | 4 +++
.../js/views/settings/main_settings_view.js | 35 ++++---------------
cms/templates/course_info.html | 1 +
cms/templates/settings.html | 1 +
6 files changed, 23 insertions(+), 44 deletions(-)
create mode 100644 cms/static/js/views/server_error.js
diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js
index 61e31133fd..bdbb46b3b1 100644
--- a/cms/static/js/models/settings/course_details.js
+++ b/cms/static/js/models/settings/course_details.js
@@ -69,17 +69,11 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
// newsource either is or just the "speed:key, *" string
// 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},
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
// TODO remove all whitespace w/in string
else {
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource,
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
}
return this.videosourceSample();
diff --git a/cms/static/js/views/course_info_edit.js b/cms/static/js/views/course_info_edit.js
index 0ad02215db..cb396b2a7f 100644
--- a/cms/static/js/views/course_info_edit.js
+++ b/cms/static/js/views/course_info_edit.js
@@ -99,10 +99,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
var targetModel = this.eventModel(event);
targetModel.set({ date : this.dateEntry(event).val(), content : this.$codeMirror.getValue() });
// push change to display, hide the editor, submit the change
- targetModel.save({}, {error : function(model, xhr) {
- // TODO use a standard component
- window.alert(xhr.responseText);
- }});
+ targetModel.save({}, {error : CMS.ServerError});
this.closeEditor(this);
},
@@ -145,8 +142,10 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
this.modelDom(event).remove();
var cacheThis = this;
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({
self.render();
}
);
- }
+ },
+ error : CMS.ServerError
}
);
},
@@ -267,7 +267,7 @@ CMS.Views.ClassInfoHandoutsView = Backbone.View.extend({
onSave: function(event) {
this.model.set('data', this.$codeMirror.getValue());
this.render();
- this.model.save();
+ this.model.save({}, {error: CMS.ServerError});
this.$form.hide();
this.closeEditor(this);
},
diff --git a/cms/static/js/views/server_error.js b/cms/static/js/views/server_error.js
new file mode 100644
index 0000000000..11478b5712
--- /dev/null
+++ b/cms/static/js/views/server_error.js
@@ -0,0 +1,4 @@
+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
diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js
index 602da60ed6..e2b5326aaf 100644
--- a/cms/static/js/views/settings/main_settings_view.js
+++ b/cms/static/js/views/settings/main_settings_view.js
@@ -55,10 +55,7 @@ CMS.Views.ValidatingView = Backbone.View.extend({
var newVal = $(event.currentTarget).val();
if (currentVal != newVal) {
this.clearValidationErrors();
- this.model.save(field, newVal, { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ this.model.save(field, newVal, { error : CMS.ServerError});
return true;
}
else return false;
@@ -231,10 +228,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
}
var newVal = new Date(date.getTime() + time * 1000);
if (cacheModel.get(fieldName) != newVal) cacheModel.save(fieldName, newVal,
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
}
};
@@ -284,10 +278,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
removeSyllabus: function() {
if (this.model.has('syllabus')) this.model.save({'syllabus': null},
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
},
assetSyllabus : function() {
@@ -321,10 +312,7 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
cachethis.clearValidationErrors();
var newVal = mirror.getValue();
if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal,
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
}
});
}
@@ -420,10 +408,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
event.data.clearValidationErrors();
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,
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
},
updateModel : function(event) {
if (!this.selectorToField[event.currentTarget.id]) return;
@@ -560,10 +545,7 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
return object;
},
{}),
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
},
addNewGrade: function(e) {
@@ -695,10 +677,7 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
},
deleteModel : function(e) {
this.model.destroy(
- { error : function(model, error) {
- // this handler is for the client:server communication not the vlidation errors which handleValidationError catches
- window.alert("Error during save: " + error.responseText);
- }});
+ { error : CMS.ServerError});
e.preventDefault();
}
diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html
index f4fa661b6e..83d829efa0 100644
--- a/cms/templates/course_info.html
+++ b/cms/templates/course_info.html
@@ -10,6 +10,7 @@
+
diff --git a/cms/templates/settings.html b/cms/templates/settings.html
index 1fa5b0acce..8cd4246da9 100644
--- a/cms/templates/settings.html
+++ b/cms/templates/settings.html
@@ -20,6 +20,7 @@ from contentstore import utils
+