+
diff --git a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
new file mode 100644
index 0000000000..60c73cf1fe
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
@@ -0,0 +1,15 @@
+xdescribe 'CombinedOpenEnded', ->
+ beforeEach ->
+ spyOn Logger, 'log'
+ # load up some fixtures
+ loadFixtures 'combined-open-ended.html'
+ @element = $('.combined-open-ended')
+
+ describe 'constructor', ->
+ beforeEach ->
+ @combined = new CombinedOpenEnded @element
+
+ it 'set the element', ->
+ except(@combined.element).not.toEqual @element
+
+ #it 'initialize the ajax url, state, and task count'
From 26cb1c5a2564ec85d3dea8f762c559397fe001b5 Mon Sep 17 00:00:00 2001
From: cahrens
Date: Tue, 29 Jan 2013 16:30:41 -0500
Subject: [PATCH 19/60] 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 20/60] 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 f1973dc660a5de9bfa24300a51e032fde151ee9c Mon Sep 17 00:00:00 2001
From: Diana Huang
Date: Tue, 29 Jan 2013 17:34:54 -0500
Subject: [PATCH 21/60] minor update to new jasmine test
---
.../xmodule/js/spec/combinedopenended/display_spec.coffee | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
index 60c73cf1fe..3ccf504948 100644
--- a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
@@ -12,4 +12,5 @@ xdescribe 'CombinedOpenEnded', ->
it 'set the element', ->
except(@combined.element).not.toEqual @element
- #it 'initialize the ajax url, state, and task count'
+ #it 'initialize the ajax url, state, and task count', ->
+
From d132f2e18a628caf972e58b4e6be1bd1ab93e3ee Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 28 Jan 2013 15:05:04 -0500
Subject: [PATCH 22/60] 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 23/60] 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 24/60] 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 25/60] 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 26/60] 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 27/60] 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 28/60] 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 29/60] 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 ce8a48405b958575add205fcc73d77ca5a4ace8a Mon Sep 17 00:00:00 2001
From: Diana Huang
Date: Wed, 30 Jan 2013 09:31:20 -0500
Subject: [PATCH 30/60] Clean up some JS errors showing up in the xmodule
jasmine tests
---
common/lib/xmodule/jasmine_test_runner.html.erb | 1 -
common/lib/xmodule/xmodule/js/spec/helper.coffee | 1 -
2 files changed, 2 deletions(-)
diff --git a/common/lib/xmodule/jasmine_test_runner.html.erb b/common/lib/xmodule/jasmine_test_runner.html.erb
index fae6c14cbe..7b078daedd 100644
--- a/common/lib/xmodule/jasmine_test_runner.html.erb
+++ b/common/lib/xmodule/jasmine_test_runner.html.erb
@@ -14,7 +14,6 @@
-
diff --git a/common/lib/xmodule/xmodule/js/spec/helper.coffee b/common/lib/xmodule/xmodule/js/spec/helper.coffee
index dc01241861..fbc89f7bd9 100644
--- a/common/lib/xmodule/xmodule/js/spec/helper.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/helper.coffee
@@ -64,7 +64,6 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
if createPlayer
return new VideoPlayer(video: context.video)
-spyOn(window, 'onunload')
# Stub jQuery.cookie
$.cookie = jasmine.createSpy('jQuery.cookie').andReturn '1.0'
From 4df62d968d9ad1f8b2f364e958d264cf469aa7ad Mon Sep 17 00:00:00 2001
From: Diana Huang
Date: Wed, 30 Jan 2013 09:31:42 -0500
Subject: [PATCH 31/60] Fix up new jasmine tests
---
.../js/spec/combinedopenended/display_spec.coffee | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
index 3ccf504948..02aaa50103 100644
--- a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
@@ -1,16 +1,15 @@
-xdescribe 'CombinedOpenEnded', ->
+describe 'CombinedOpenEnded', ->
beforeEach ->
spyOn Logger, 'log'
# load up some fixtures
loadFixtures 'combined-open-ended.html'
@element = $('.combined-open-ended')
- describe 'constructor', ->
- beforeEach ->
- @combined = new CombinedOpenEnded @element
- it 'set the element', ->
- except(@combined.element).not.toEqual @element
+ describe 'constructor', ->
+ beforeEach ->
+ @combined = new CombinedOpenEnded @element
+ it 'set the element', ->
+ expect(@combined.element).toEqual @element
- #it 'initialize the ajax url, state, and task count', ->
From ab4fd03dd8c5b42afcaa0258e27d51245d613671 Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Wed, 30 Jan 2013 09:43:27 -0500
Subject: [PATCH 32/60] 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 33/60] 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 dbf15fc2ac5322660446b979a7f3e7b31087d7ec Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Wed, 30 Jan 2013 09:46:38 -0500
Subject: [PATCH 34/60] add comment on draft store test which explains test
success is just for get_items() not to throw an exception when passed in a
course_id
---
lms/djangoapps/courseware/tests/tests.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index fad8ac6fef..6c41cbac14 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -445,6 +445,8 @@ class TestDraftModuleStore(TestCase):
store = modulestore()
# fix was to allow get_items() to take the course_id parameter
store.get_items(Location(None, None, 'vertical', None, None), course_id='abc', depth=0)
+ # test success is just getting through the above statement. The bug was that 'course_id' argument was
+ # not allowed to be passed in (i.e. was throwing exception)
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
From 99dd9636ced75a0af3fee003775591dcb9c91f2f Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Wed, 30 Jan 2013 10:43:36 -0500
Subject: [PATCH 35/60] 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 b38595f115a952e8fec27c21457da99d14422d6a Mon Sep 17 00:00:00 2001
From: Diana Huang
Date: Wed, 30 Jan 2013 10:50:47 -0500
Subject: [PATCH 36/60] Fix the fixtures, add new tests, remove useless code
from the original JS
---
.../js/fixtures/combined-open-ended.html | 71 ++++++++++++++++++-
.../combinedopenended/display_spec.coffee | 16 ++++-
.../js/src/combinedopenended/display.coffee | 1 -
3 files changed, 84 insertions(+), 4 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html b/common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html
index c7170d3472..447b49b8fb 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html
@@ -1,4 +1,6 @@
-
+
+
+
CAMBRIDGE, MA – January 30, 2013 –
+In the past 10 years, the ability to decode or “sequence” DNA has grown by a million-fold, a stunning rate of progress that is producing a flood of information about human biology and disease. Because of these advances, the scientific community — and the world as a whole — stands on the verge of a revolution in biology. In the coming decades scientists will be able to understand how cells are “wired” and how that wiring is disrupted in human diseases ranging from diabetes to cancer to schizophrenia. Now, with his free online course, 7.00x Introductory Biology: “The Secret of Life”, genome pioneer Eric Lander, the founding director of the Broad Institute and a professor at MIT and Harvard Medical School, will explain to students around the world the basics of biology – the secret of life, so to speak – so that they can understand today’s revolution in biology.
+
+
EdX, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), brings the best courses from the best faculty at the best institutions to anyone with an Internet connection. For the past 20 years, legendary teacher Lander has taught Introductory Biology to more than half of all MIT students. He has now adapted his course for online education, creating the newest course on the edX platform. The course, 7.00X, is now open for enrollment, with the first class slated for March 5th. This course will include innovative technology including a 3D molecule viewer and gene explorer tool to transform the learning experience. It is open to all levels and types of learners.
+
+
“Introducing the freshman class of MIT to the basics of biology is exhilarating,” said Lander. “Now, with this edX course, I look forward to teaching people around the world. There are no prerequisites for this course – other than curiosity and an interest in understanding some of the greatest scientific challenges of our time.”
+
+
Those taking the course will learn the fundamental ideas that underlie modern biology and medicine, including genetics, biochemistry, molecular biology, recombinant DNA, genomics and genomic medicine. They will become familiar with the structure and function of macromolecules such as DNA, RNA and proteins and understand how information flows within cells. Students will explore how mutations affect biological function and cause human disease. They will learn about modern molecular biological techniques and their wide-ranging impact.
+
+
“Eric Lander has created this remarkable digitally enhanced introduction to genetics and biology,” said Anant Agarwal, President of edX. “With this unique online version, he has brought the introductory biology course to a new level. It has been completely rethought and retooled, incorporating cutting-edge online interactive tools as well as community-building contests and milestone-based prizes.”
+
+
With online courses through edX like 7.00x, what matters isn’t what people have achieved or their transcripts, but their desire to learn. Students only need to come with a real interest in science and the desire to understand what's going on at the forefront of biology, and to learn the fundamental principles on which an amazing biomedical revolution is based – from one of the top scientist in the world. 7.00x Introductory Biology: The Secret of Life is now available for enrollment. Classes will start on March 5, 2013.
+
+
Dr. Eric Lander is President and Founding Director of the Broad Institute of Harvard and MIT, a new kind of collaborative biomedical research institution focused on genomic medicine. Dr. Lander is also Professor of Biology at MIT and Professor of Systems Biology at the Harvard Medical School. In addition, Dr. Lander serves as Co-Chair of the President’s Council of Advisors on Science and Technology, which advises the White House on science and technology. A geneticist, molecular biologist and mathematician, Dr. Lander has played a pioneering role in all aspects of the reading, understanding and medical application of the human genome. He was a principal leader of the international Human Genome Project (HGP) from 1990-2003, with his group being the largest contributor to the mapping and sequencing of the human genetic blueprint. Dr. Lander was an early pioneer in the free availability of genomic tools and information. Finally, he has mentored an extraordinary cadre of young scientists who have become the next generation of leaders in medical genomics. The recipient of numerous awards and honorary degrees, Dr. Lander was elected a member of the U.S. National Academy of Sciences in 1997 and of the U.S. Institute of Medicine in 1999.
The Eli and Edythe L. Broad Institute of MIT and Harvard was founded in 2003 to empower this generation of creative scientists to transform medicine with new genome-based knowledge. The Broad Institute seeks to describe all the molecular components of life and their connections; discover the molecular basis of major human diseases; develop effective new approaches to diagnostics and therapeutics; and disseminate discoveries, tools, methods and data openly to the entire scientific community.
+
+
Founded by MIT, Harvard and its affiliated hospitals, and the visionary Los Angeles philanthropists Eli and Edythe L. Broad, the Broad Institute includes faculty, professional staff and students from throughout the MIT and Harvard biomedical research communities and beyond, with collaborations spanning over a hundred private and public institutions in more than 40 countries worldwide. For further information about the Broad Institute, go to www.broadinstitute.org.
+
+
About edX
+
+
EdX is a not-for-profit enterprise of its founding partners Harvard University and the Massachusetts Institute of Technology focused on transforming online and on-campus learning through groundbreaking methodologies, game-like experiences and cutting-edge research. EdX provides inspirational and transformative knowledge to students of all ages, social status, and income who form worldwide communities of learners. EdX uses its open source technology to transcend physical and social borders. We’re focused on people, not profit. EdX is based in Cambridge, Massachusetts in the USA.
EdX is a not-for-profit enterprise of its founding partners Harvard University and the Massachusetts Institute of Technology focused on transforming online and on-campus learning through groundbreaking methodologies, game-like experiences and cutting-edge research. EdX provides inspirational and transformative knowledge to students of all ages, social status, and income who form worldwide communities of learners. EdX uses its open source technology to transcend physical and social borders. We’re focused on people, not profit. EdX is based in Cambridge, Massachusetts in the USA.
diff --git a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
index d4aa2b5b6c..7c26bda213 100644
--- a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
@@ -19,14 +19,11 @@ describe 'CombinedOpenEnded', ->
expect(@combined.ajax_url).toEqual '/courses/MITx/6.002x/2012_Fall/modx/i4x://MITx/6.002x/combinedopenended/CombinedOE'
expect(@combined.state).toEqual 'assessing'
expect(@combined.task_count).toEqual 2
- expect(@combined.task_number).toEqual 2
+ expect(@combined.task_number).toEqual 1
it 'subelements are made collapsible', ->
expect(Collapsible.setCollapsibles).toHaveBeenCalled()
- it 'elements are rebound for assessing state', ->
- expect(@combined.answer_area.attr("disabled")).toBe("disabled")
- expect(@combined.submit_button.val()).toBe("Submit assessment")
describe 'poll', ->
beforeEach =>
@@ -35,14 +32,14 @@ describe 'CombinedOpenEnded', ->
spyOn(@combined, 'reload').andCallFake -> return 0
window.setTimeout = jasmine.createSpy().andCallFake (callback, timeout) -> return 5
- it 'we are setting the timeout', =>
+ it 'polls at the correct intervals', =>
fakeResponseContinue = state: 'not done'
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(fakeResponseContinue)
@combined.poll()
expect(window.setTimeout).toHaveBeenCalledWith(@combined.poll, 10000)
expect(window.queuePollerID).toBe(5)
- it 'we are stopping polling properly', =>
+ it 'polling stops properly', =>
$.postWithPrefix = jasmine.createSpy("$.postWithPrefix")
fakeResponseDone = state: "done"
$.postWithPrefix.andCallFake (url, callback) -> callback(fakeResponseDone)
@@ -50,3 +47,36 @@ describe 'CombinedOpenEnded', ->
expect(window.queuePollerID).toBeUndefined()
expect(window.setTimeout).not.toHaveBeenCalled()
expect(@combined.reload).toHaveBeenCalled()
+
+ describe 'rebind', ->
+ beforeEach ->
+ @combined = new CombinedOpenEnded @element
+ spyOn(@combined, 'queueing').andCallFake -> return 0
+ spyOn(@combined, 'skip_post_assessment').andCallFake -> return 0
+ window.setTimeout = jasmine.createSpy().andCallFake (callback, timeout) -> return 5
+
+ it 'when our child is in an assessing state', ->
+ @combined.child_state = 'assessing'
+ @combined.rebind()
+ expect(@combined.answer_area.attr("disabled")).toBe("disabled")
+ expect(@combined.submit_button.val()).toBe("Submit assessment")
+ expect(@combined.queueing).toHaveBeenCalled()
+
+ it 'when our child state is initial', ->
+ @combined.child_state = 'initial'
+ @combined.rebind()
+ expect(@combined.answer_area.attr("disabled")).toBeUndefined()
+ expect(@combined.submit_button.val()).toBe("Submit")
+
+ it 'when our child state is post_assessment', ->
+ @combined.child_state = 'post_assessment'
+ @combined.rebind()
+ expect(@combined.answer_area.attr("disabled")).toBe("disabled")
+ expect(@combined.submit_button.val()).toBe("Submit post-assessment")
+
+ it 'when our child state is done', ->
+ spyOn(@combined, 'next_problem').andCallFake ->
+ @combined.child_state = 'done'
+ @combined.rebind()
+ expect(@combined.answer_area.attr("disabled")).toBe("disabled")
+ expect(@combined.next_problem).toHaveBeenCalled()
From c4f56620dfb610d9a1f105478cfe4da6017102c3 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Wed, 30 Jan 2013 16:49:49 -0500
Subject: [PATCH 51/60] Revert "Merge pull request #1374 from
MITx/feature/cale/no-course-collectstatic"
This reverts commit ab437b946394e7fa9123f5ea91e31e671a59ead9, reversing
changes made to 7a8e87c789f500f2767a0b85bfdac2320fe0ca46.
---
cms/djangoapps/contentstore/views.py | 138 ++++++++++-----------
common/lib/xmodule/xmodule/capa_module.py | 10 +-
lms/djangoapps/courseware/courses.py | 5 +-
lms/djangoapps/courseware/module_render.py | 10 +-
lms/envs/common.py | 20 ++-
lms/envs/dev.py | 21 ----
6 files changed, 98 insertions(+), 106 deletions(-)
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 0d006fdab0..b8981ecaa0 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,23 +511,23 @@ 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),
+ 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(),
@@ -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()
@@ -952,7 +952,7 @@ def reorder_static_tabs(request):
@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)
@@ -981,7 +981,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
})
@@ -1002,13 +1002,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"]
@@ -1019,7 +1019,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
@@ -1033,7 +1033,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 == '':
@@ -1048,7 +1048,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
@@ -1065,7 +1065,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()
@@ -1078,10 +1078,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")
@@ -1099,20 +1099,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
@@ -1135,13 +1135,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
@@ -1154,7 +1154,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
@@ -1165,13 +1165,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.
@@ -1188,7 +1188,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()
@@ -1201,7 +1201,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)
@@ -1215,15 +1215,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', {
@@ -1242,9 +1242,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))
@@ -1290,13 +1290,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
@@ -1390,7 +1390,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)
@@ -1402,7 +1402,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 6b536587c1..f33da6e3a4 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -356,7 +356,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)
+ return self.system.replace_urls(html, self.metadata['data_dir'], course_namespace=self.location)
def handle_ajax(self, dispatch, get):
'''
@@ -461,7 +461,7 @@ class CapaModule(XModule):
new_answers = dict()
for answer_id in answers:
try:
- new_answer = {answer_id: self.system.replace_urls(answers[answer_id])}
+ new_answer = {answer_id: self.system.replace_urls(answers[answer_id], self.metadata['data_dir'], course_namespace=self.location)}
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]}
@@ -669,18 +669,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
diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py
index 03d5a89c64..89a1496eca 100644
--- a/lms/djangoapps/courseware/courses.py
+++ b/lms/djangoapps/courseware/courses.py
@@ -83,12 +83,13 @@ 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):
- return '/static/' + course.metadata['data_dir'] + "/images/course_image.jpg"
+ path = course.metadata['data_dir'] + "/images/course_image.jpg"
+ return try_staticfiles_lookup(path)
else:
loc = course.location._replace(tag='c4x', category='asset', name='images_course_image.jpg')
path = StaticContent.get_url_path_from_location(loc)
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 22d95ef8a2..7ed32c8597 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -3,8 +3,6 @@ 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
@@ -246,11 +244,7 @@ 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=partial(
- replace_urls,
- staticfiles_prefix='/static/' + descriptor.metadata.get('data_dir', ''),
- course_namespace=descriptor.location._replace(category=None, name=None),
- ),
+ replace_urls=replace_urls,
node_path=settings.NODE_PATH,
anonymous_student_id=unique_id_for_user(user),
course_id=course_id,
@@ -286,7 +280,7 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
module.get_html = replace_static_urls(
_get_html,
- '/static/' + module.metadata.get('data_dir', ''),
+ 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
diff --git a/lms/envs/common.py b/lms/envs/common.py
index a2d9614109..16472795e0 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -266,6 +266,24 @@ 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
@@ -450,7 +468,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 338a31f641..99ee9662ee 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -106,27 +106,6 @@ 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 756a0716349ced44c93bb06b674876a2c0912d72 Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Wed, 30 Jan 2013 17:08:21 -0500
Subject: [PATCH 52/60] misspelled request (altho as an unused identifier)
---
cms/djangoapps/contentstore/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 591f474c8f..14f96e312a 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -1426,7 +1426,7 @@ def export_course(request, org, course, name):
'successful_import_redirect_url' : ''
})
-def event(requet):
+def event(request):
'''
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
console logs don't get distracted :-)
From 8144a1dfafe3bc8115b514565443748bf38d93ef Mon Sep 17 00:00:00 2001
From: Diana Huang
Date: Wed, 30 Jan 2013 17:25:13 -0500
Subject: [PATCH 53/60] Add in next_problem jasmine tests
---
.../combinedopenended/display_spec.coffee | 34 +++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
index 7c26bda213..6777d8c794 100644
--- a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
@@ -40,9 +40,8 @@ describe 'CombinedOpenEnded', ->
expect(window.queuePollerID).toBe(5)
it 'polling stops properly', =>
- $.postWithPrefix = jasmine.createSpy("$.postWithPrefix")
fakeResponseDone = state: "done"
- $.postWithPrefix.andCallFake (url, callback) -> callback(fakeResponseDone)
+ spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(fakeResponseDone)
@combined.poll()
expect(window.queuePollerID).toBeUndefined()
expect(window.setTimeout).not.toHaveBeenCalled()
@@ -80,3 +79,34 @@ describe 'CombinedOpenEnded', ->
@combined.rebind()
expect(@combined.answer_area.attr("disabled")).toBe("disabled")
expect(@combined.next_problem).toHaveBeenCalled()
+
+ describe 'next_problem', ->
+ beforeEach ->
+ @combined = new CombinedOpenEnded @element
+ @combined.child_state = 'done'
+
+ it 'handling a successful call', ->
+ fakeResponse =
+ success: true
+ html: "dummy html"
+ allow_reset: false
+ spyOn($, 'postWithPrefix').andCallFake (url, val, callback) -> callback(fakeResponse)
+ spyOn(@combined, 'reinitialize')
+ spyOn(@combined, 'rebind')
+ @combined.next_problem()
+ expect($.postWithPrefix).toHaveBeenCalled()
+ expect(@combined.reinitialize).toHaveBeenCalledWith(@combined.element)
+ expect(@combined.rebind).toHaveBeenCalled()
+ expect(@combined.answer_area.val()).toBe('')
+ expect(@combined.child_state).toBe('initial')
+
+ it 'handling an unsuccessful call', ->
+ fakeResponse =
+ success: false
+ error: 'This is an error'
+ spyOn($, 'postWithPrefix').andCallFake (url, val, callback) -> callback(fakeResponse)
+ @combined.next_problem()
+ expect(@combined.errors_area.html()).toBe(fakeResponse.error)
+
+
+
From 32524727ce6b31a343e1afcbbb8d838ae1eeb8f6 Mon Sep 17 00:00:00 2001
From: Diana Huang
Date: Thu, 31 Jan 2013 09:00:06 -0500
Subject: [PATCH 54/60] Create a test framework and make it so that we force a
mock backend when we don't have an ajax url
---
lms/static/coffee/fixtures/staff_grading.html | 76 +++++++++++++++++++
.../coffee/spec/staff_grading_spec.coffee | 11 +++
.../src/staff_grading/staff_grading.coffee | 8 +-
3 files changed, 93 insertions(+), 2 deletions(-)
create mode 100644 lms/static/coffee/fixtures/staff_grading.html
create mode 100644 lms/static/coffee/spec/staff_grading_spec.coffee
diff --git a/lms/static/coffee/fixtures/staff_grading.html b/lms/static/coffee/fixtures/staff_grading.html
new file mode 100644
index 0000000000..2fe5a39a17
--- /dev/null
+++ b/lms/static/coffee/fixtures/staff_grading.html
@@ -0,0 +1,76 @@
+
+
+
+
Staff grading
+
+
+
+
+
+
+
+
+
+
Instructions
+
+
This is the list of problems that current need to be graded in order to train the machine learning models. Each problem needs to be trained separately, and we have indicated the number of student submissions that need to be graded in order for a model to be generated. You can grade more than the minimum required number of submissions--this will improve the accuracy of machine learning, though with diminishing returns. You can see the current accuracy of machine learning while grading.
+
+
+
Problem List
+
+
+
+
+
+
+
+
+
+
Problem Information
+
+
+
Maching Learning Information
+
+
+
+
+
Question
+
+
+
+
+
+
+
+
+
+
+
+
Grading
+
+
+
+
Student Submission
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lms/static/coffee/spec/staff_grading_spec.coffee b/lms/static/coffee/spec/staff_grading_spec.coffee
new file mode 100644
index 0000000000..595a9eb550
--- /dev/null
+++ b/lms/static/coffee/spec/staff_grading_spec.coffee
@@ -0,0 +1,11 @@
+describe 'StaffGrading', ->
+ beforeEach ->
+ spyOn Logger, 'log'
+ @mockBackend = new StaffGradingBackend('url', true)
+
+ describe 'constructor', ->
+ beforeEach ->
+ @staff_grading = new StaffGrading(@mockBackend)
+
+ it 'we are originally in the list view', ->
+ expect(@staff_grading.list_view).toBe(true)
diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee
index c7a8a01bab..005a8e682e 100644
--- a/lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -9,9 +9,13 @@ state_graded = "graded"
state_no_data = "no_data"
state_error = "error"
-class StaffGradingBackend
+class @StaffGradingBackend
constructor: (ajax_url, mock_backend) ->
@ajax_url = ajax_url
+ # prevent this from trying to make requests when we don't have
+ # a proper url
+ if !ajax_url
+ mock_backend = true
@mock_backend = mock_backend
if @mock_backend
@mock_cnt = 0
@@ -142,7 +146,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
.error => callback({success: false, error: "Error occured while performing this operation"})
-class StaffGrading
+class @StaffGrading
constructor: (backend) ->
@backend = backend
From 0d20c192b9955a955cc2a235c0d27fc87aa3d3ab Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Thu, 31 Jan 2013 09:57:07 -0500
Subject: [PATCH 55/60] add exporting of course info
---
common/lib/xmodule/xmodule/modulestore/xml_exporter.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
index 5e85cd6fc5..3522b45718 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py
@@ -23,6 +23,9 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
# export the custom tags
export_extra_content(export_fs, modulestore, course_location, 'custom_tag_template', 'custom_tags')
+ # export the course updates
+ export_extra_content(export_fs, modulestore, course_location, 'course_info', 'info', '.html')
+
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)
From fc57e7afdb8bfd54e934bbebb4cfd13a35adc673 Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Thu, 31 Jan 2013 10:51:20 -0500
Subject: [PATCH 56/60] add testing for course info export. Plus DRY things out
a bit
---
cms/djangoapps/contentstore/tests/tests.py | 34 +++++++++++-----------
common/test/data/full/tabs/resources.html | 2 +-
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 8c520eb39c..b503a1819d 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -418,6 +418,17 @@ class ContentStoreTest(TestCase):
items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None]))
self.assertEqual(len(items), 0)
+ def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''):
+ fs = OSFS(root_dir / 'test_export')
+ self.assertTrue(fs.exists(dirname))
+
+ query_loc = Location('i4x', location.org, location.course, category_name, None)
+ items = modulestore.get_items(query_loc)
+
+ for item in items:
+ fs = OSFS(root_dir / ('test_export/' + dirname))
+ self.assertTrue(fs.exists(item.location.name + filename_suffix))
+
def test_export_course(self):
ms = modulestore('direct')
cs = contentstore()
@@ -433,26 +444,14 @@ class ContentStoreTest(TestCase):
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'))
+ self.verify_content_existence(ms, root_dir, location, 'tabs', 'static_tab', '.html')
# check for custom_tags
- fs = OSFS(root_dir / 'test_export')
- self.assertTrue(fs.exists('custom_tags'))
+ self.verify_content_existence(ms, root_dir, location, 'info', 'course_info', '.html')
- 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))
+ # check for custom_tags
+ self.verify_content_existence(ms, root_dir, location, 'custom_tags', 'custom_tag_template')
+
# remove old course
delete_course(ms, cs, location)
@@ -469,6 +468,7 @@ class ContentStoreTest(TestCase):
shutil.rmtree(root_dir)
+
def test_course_handouts_rewrites(self):
ms = modulestore('direct')
cs = contentstore()
diff --git a/common/test/data/full/tabs/resources.html b/common/test/data/full/tabs/resources.html
index 62e77a8416..bf78c92fb1 100644
--- a/common/test/data/full/tabs/resources.html
+++ b/common/test/data/full/tabs/resources.html
@@ -1 +1 @@
-This is another sample tab
\ No newline at end of file
+
This is another sample tab
\ No newline at end of file
From 649136364784708993b6b6dde00f7813aedf188d Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Thu, 31 Jan 2013 12:11:11 -0500
Subject: [PATCH 57/60] Remove format field as it's the one for grading type.
---
cms/templates/edit_subsection.html | 4 ----
1 file changed, 4 deletions(-)
diff --git a/cms/templates/edit_subsection.html b/cms/templates/edit_subsection.html
index c3aed8a94f..de5e14e0a9 100644
--- a/cms/templates/edit_subsection.html
+++ b/cms/templates/edit_subsection.html
@@ -23,10 +23,6 @@
-
-
-
-
${units.enum_units(subsection, subsection_units=subsection_units)}
From f257f12124be03a8bdd057cbc1169dc7e5f6bc3a Mon Sep 17 00:00:00 2001
From: Don Mitchell
Date: Thu, 31 Jan 2013 12:56:18 -0500
Subject: [PATCH 58/60] Meant to add the file here
---
cms/static/coffee/files.json | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/cms/static/coffee/files.json b/cms/static/coffee/files.json
index ec596063a9..9e84c39f03 100644
--- a/cms/static/coffee/files.json
+++ b/cms/static/coffee/files.json
@@ -1,9 +1,12 @@
{
"js_files": [
+ "/static/js/vendor/RequireJS.js",
"/static/js/vendor/jquery.min.js",
+ "js/vendor/jquery-ui.min.js",
+ "js/vendor/jquery.ui.draggable.js",
+ "js/vendor/jquery.cookie.js",
"/static/js/vendor/json2.js",
"/static/js/vendor/underscore-min.js",
- "/static/js/vendor/backbone-min.js",
- "/static/js/vendor/RequireJS.js"
+ "/static/js/vendor/backbone-min.js"
]
}
From b613bdc3366bdef89511355142e2829c515a51de Mon Sep 17 00:00:00 2001
From: cahrens
Date: Thu, 31 Jan 2013 13:37:45 -0500
Subject: [PATCH 59/60] Fixes date bug (#149).
---
.../js/views/settings/main_settings_view.js | 96 ++++++++++---------
1 file changed, 50 insertions(+), 46 deletions(-)
diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js
index e2b5326aaf..826b385dff 100644
--- a/cms/static/js/views/settings/main_settings_view.js
+++ b/cms/static/js/views/settings/main_settings_view.js
@@ -211,15 +211,15 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
'intro_video' : 'course-introduction-video',
'effort' : "course-effort"
},
-
- setupDatePicker : function(fieldName) {
- var cacheModel = this.model;
- var div = this.$el.find('#' + this.fieldToSelectorMap[fieldName]);
- var datefield = $(div).find(".date");
- var timefield = $(div).find(".time");
- var cachethis = this;
- var savefield = function() {
- cachethis.clearValidationErrors();
+
+ setupDatePicker: function (fieldName) {
+ var cacheModel = this.model;
+ var div = this.$el.find('#' + this.fieldToSelectorMap[fieldName]);
+ var datefield = $(div).find(".date");
+ var timefield = $(div).find(".time");
+ var cachethis = this;
+ var savefield = function () {
+ cachethis.clearValidationErrors();
var date = datefield.datepicker('getDate');
if (date) {
var time = timefield.timepicker("getSecondsFromMidnight");
@@ -227,21 +227,24 @@ 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,
- { error : CMS.ServerError});
+ if (cacheModel.get(fieldName).getTime() !== newVal.getTime()) {
+ cacheModel.save(fieldName, newVal, { error: CMS.ServerError});
+ }
}
- };
-
- // instrument as date and time pickers
- timefield.timepicker();
-
- // FIXME being called 2x on each change. Was trapping datepicker onSelect b4 but change to datepair broke that
- datefield.datepicker({ onSelect : savefield });
- timefield.on('changeTime', savefield);
-
- datefield.datepicker('setDate', this.model.get(fieldName));
- if (this.model.has(fieldName)) timefield.timepicker('setTime', this.model.get(fieldName));
- },
+ };
+
+ // instrument as date and time pickers
+ timefield.timepicker();
+ datefield.datepicker();
+
+ // Using the change event causes savefield to be triggered twice, but it is necessary
+ // to pick up when the date is typed directly in the field.
+ datefield.change(savefield);
+ timefield.on('changeTime', savefield);
+
+ datefield.datepicker('setDate', this.model.get(fieldName));
+ if (this.model.has(fieldName)) timefield.timepicker('setTime', this.model.get(fieldName));
+ },
updateModel: function(event) {
switch (event.currentTarget.id) {
@@ -294,29 +297,30 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
}
},
codeMirrors : {},
- codeMirrorize : function(e, forcedTarget) {
- if (forcedTarget) {
- thisTarget = forcedTarget;
- thisTarget.id = $(thisTarget).attr('id');
- } else {
- thisTarget = e.currentTarget;
- }
+ codeMirrorize: function (e, forcedTarget) {
+ var thisTarget;
+ if (forcedTarget) {
+ thisTarget = forcedTarget;
+ thisTarget.id = $(thisTarget).attr('id');
+ } else {
+ thisTarget = e.currentTarget;
+ }
- if (!this.codeMirrors[thisTarget.id]) {
- var cachethis = this;
- var field = this.selectorToField[thisTarget.id];
- this.codeMirrors[thisTarget.id] = CodeMirror.fromTextArea(thisTarget, {
- mode: "text/html", lineNumbers: true, lineWrapping: true,
- onBlur : function(mirror) {
- mirror.save();
- cachethis.clearValidationErrors();
- var newVal = mirror.getValue();
- if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal,
- { error : CMS.ServerError});
- }
- });
- }
- }
+ if (!this.codeMirrors[thisTarget.id]) {
+ var cachethis = this;
+ var field = this.selectorToField[thisTarget.id];
+ this.codeMirrors[thisTarget.id] = CodeMirror.fromTextArea(thisTarget, {
+ mode: "text/html", lineNumbers: true, lineWrapping: true,
+ onBlur: function (mirror) {
+ mirror.save();
+ cachethis.clearValidationErrors();
+ var newVal = mirror.getValue();
+ if (cachethis.model.get(field) != newVal) cachethis.model.save(field, newVal,
+ { error: CMS.ServerError});
+ }
+ });
+ }
+ }
});
@@ -668,7 +672,7 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
$(event.currentTarget).parent().append(
this.errorTemplate({message : 'For grading to work, you must change all "' + oldName +
'" subsections to "' + this.model.get('type') + '".'}));
- };
+ }
break;
default:
this.saveIfChanged(event);
From c590c24e25729caefc8f4135d923fed4d1bcee0b Mon Sep 17 00:00:00 2001
From: Jay Zoldak
Date: Thu, 31 Jan 2013 13:48:38 -0500
Subject: [PATCH 60/60] Add vendor files to files.json for the cms jasmine
tests to work.
---
cms/static/coffee/files.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cms/static/coffee/files.json b/cms/static/coffee/files.json
index 9e84c39f03..2249813b04 100644
--- a/cms/static/coffee/files.json
+++ b/cms/static/coffee/files.json
@@ -2,9 +2,9 @@
"js_files": [
"/static/js/vendor/RequireJS.js",
"/static/js/vendor/jquery.min.js",
- "js/vendor/jquery-ui.min.js",
- "js/vendor/jquery.ui.draggable.js",
- "js/vendor/jquery.cookie.js",
+ "/static/js/vendor/jquery-ui.min.js",
+ "/static/js/vendor/jquery.ui.draggable.js",
+ "/static/js/vendor/jquery.cookie.js",
"/static/js/vendor/json2.js",
"/static/js/vendor/underscore-min.js",
"/static/js/vendor/backbone-min.js"