From 994102b16e0c16144d60d2658a614d23d481c3b7 Mon Sep 17 00:00:00 2001
From: David Ormsbee
Date: Fri, 7 Sep 2012 16:02:00 -0400
Subject: [PATCH 01/82] Move QueryCountDebugMiddleware inclusion to dev.py
instead of common.py -- it was too chatty in the logs
---
lms/envs/common.py | 1 -
lms/envs/dev.py | 3 ++-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 55fa5b457c..1d33100ee7 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -394,7 +394,6 @@ MIDDLEWARE_CLASSES = (
# 'debug_toolbar.middleware.DebugToolbarMiddleware',
'django_comment_client.utils.ViewNameMiddleware',
- 'django_comment_client.utils.QueryCountDebugMiddleware',
)
############################### Pipeline #######################################
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index 50befeb875..b90cd97130 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -133,7 +133,8 @@ MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar',)
-MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
+MIDDLEWARE_CLASSES += ('django_comment_client.utils.QueryCountDebugMiddleware',
+ 'debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1',)
DEBUG_TOOLBAR_PANELS = (
From 03ad061a2f91c8b47eb0252999470cba8350dc32 Mon Sep 17 00:00:00 2001
From: Kyle Fiedler
Date: Thu, 13 Sep 2012 20:10:45 -0400
Subject: [PATCH 02/82] style for submission feedback
---
common/lib/xmodule/xmodule/css/capa/display.scss | 13 +++++++++++++
lms/templates/problem.html | 4 ++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 0591a01843..63b9991a9a 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -432,6 +432,19 @@ section.problem {
input.save {
@extend .blue-button;
}
+
+ .submission_feedback {
+ // background: #F3F3F3;
+ // border: 1px solid #ddd;
+ // @include border-radius(3px);
+ // padding: 8px 12px;
+ // margin-top: 10px;
+ @include inline-block;
+ font-style: italic;
+ margin: 8px 0 0 10px;
+ color: #777;
+ -webkit-font-smoothing: antialiased;
+ }
}
.detailed-solution {
diff --git a/lms/templates/problem.html b/lms/templates/problem.html
index 65b8193df9..2e94bc963f 100644
--- a/lms/templates/problem.html
+++ b/lms/templates/problem.html
@@ -13,7 +13,7 @@
% if check_button:
-
+
% endif
% if reset_button:
@@ -26,7 +26,7 @@
% endif
% if attempts_allowed :
- You have used ${ attempts_used } of ${ attempts_allowed } submissions
+ You have used ${ attempts_used } of ${ attempts_allowed } submissions
% endif
From 0170fcc01c3466f3fb8a8cb558d7070a0d6ef5cc Mon Sep 17 00:00:00 2001
From: Kyle Fiedler
Date: Fri, 14 Sep 2012 13:25:55 -0400
Subject: [PATCH 03/82] styles for a slightly better multiple choice and
solution span
---
.../lib/capa/capa/templates/choicegroup.html | 22 +++++++------
.../lib/xmodule/xmodule/css/capa/display.scss | 32 +++++++++++++++++++
2 files changed, 44 insertions(+), 10 deletions(-)
diff --git a/common/lib/capa/capa/templates/choicegroup.html b/common/lib/capa/capa/templates/choicegroup.html
index cb11350a63..106694ea4a 100644
--- a/common/lib/capa/capa/templates/choicegroup.html
+++ b/common/lib/capa/capa/templates/choicegroup.html
@@ -1,14 +1,4 @@
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 0591a01843..ba3d7e716c 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -36,9 +36,24 @@ section.problem {
}
.choicegroup {
+ @include clearfix;
+
label.choicegroup_correct:after {
content: url('../images/correct-icon.png');
}
+
+ > span {
+ padding-right: 20px;
+ float: left;
+ background-position: 0 0 !important;
+ }
+
+ fieldset {
+ @include box-sizing(border-box);
+ float: left;
+ border-left: 1px solid #ddd;
+ padding-left: 20px;
+ }
}
ol.enumerate {
@@ -52,6 +67,23 @@ section.problem {
}
}
+ .solution-span {
+ span {
+ margin: 20px 0;
+ display: block;
+ border: 1px solid #ddd;
+ padding: 9px 15px 20px;
+ background: #FFF;
+ position: relative;
+ @include box-shadow(inset 0 0 0 1px #eee);
+ @include border-radius(3px);
+
+ &:empty {
+ display: none;
+ }
+ }
+ }
+
div {
p {
&.answer {
From 6472e0ebe338584dd83959df0c27fd70d73159d8 Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Fri, 14 Sep 2012 14:29:31 -0400
Subject: [PATCH 04/82] If there's a ?next param, redirect there instead of
dashboard after login
---
lms/templates/login_modal.html | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html
index d7d327178c..0b19399fc0 100644
--- a/lms/templates/login_modal.html
+++ b/lms/templates/login_modal.html
@@ -46,7 +46,11 @@
(function() {
$(document).delegate('#login_form', 'ajax:success', function(data, json, xhr) {
if(json.success) {
- location.href="${reverse('dashboard')}";
+ % if request.REQUEST.get('next', False):
+ location.href="${request.REQUEST.get('next')}";
+ % else:
+ location.href="${reverse('dashboard')}";
+ % endif
} else {
if($('#login_error').length == 0) {
$('#login_form').prepend('');
From a788db53e90de9475dfa889ee883ac4f04a257e0 Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Fri, 14 Sep 2012 14:31:04 -0400
Subject: [PATCH 05/82] Show login modal if there's a next param to index,
university index
* also replace the external-auth-related popping up of the signup form with jquery.
Presumably it takes care of the browser specific stuff.
---
common/djangoapps/student/views.py | 2 ++
lms/djangoapps/courseware/views.py | 3 +++
lms/templates/index.html | 30 ++++++------------------------
3 files changed, 11 insertions(+), 24 deletions(-)
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index cbb12e44cc..a70349bec3 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -82,6 +82,8 @@ def index(request, extra_context={}, user=None):
domain=domain)
context = {'universities': universities, 'entries': entries}
context.update(extra_context)
+ if request.REQUEST.get('next', False):
+ context['show_login_immediately'] = True
return render_to_response('index.html', context)
def course_from_id(course_id):
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index c474da8d8b..7da5d06741 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -404,6 +404,9 @@ def university_profile(request, org_id):
context = dict(courses=courses, org_id=org_id)
template_file = "university_profile/{0}.html".format(org_id).lower()
+ if request.REQUEST.get('next', False):
+ context['show_login_immediately'] = True
+
return render_to_response(template_file, context)
def render_notifications(request, course, notifications):
diff --git a/lms/templates/index.html b/lms/templates/index.html
index 0ee00b57c0..8cabe62f09 100644
--- a/lms/templates/index.html
+++ b/lms/templates/index.html
@@ -147,28 +147,10 @@
% if show_signup_immediately is not UNDEFINED:
-% endif
+% elif show_login_immediately is not UNDEFINED:
+
+% endif
\ No newline at end of file
From 210da2c4f04c29073c5603c7880e986f0f4fe37a Mon Sep 17 00:00:00 2001
From: kimth
Date: Fri, 14 Sep 2012 16:53:23 -0400
Subject: [PATCH 06/82] Start to remember caption state
---
common/lib/xmodule/xmodule/js/src/video/display.coffee | 6 ++++++
.../xmodule/js/src/video/display/video_caption.coffee | 6 ++++--
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/video/display.coffee b/common/lib/xmodule/xmodule/js/src/video/display.coffee
index 3880091661..90e465d0ec 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display.coffee
@@ -9,6 +9,12 @@ class @Video
@parseVideos @el.data('streams')
@fetchMetadata()
@parseSpeed()
+
+ if $.cookie('hide_captions') == 'true'
+ @el.addClass('closed')
+ else
+ @el.removeClass('closed')
+
$("#video_#{@id}").data('video', this).addClass('video-load-complete')
if YT.Player
diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee
index f65debb1a2..ae6b0c8ed1 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee
@@ -1,6 +1,6 @@
class @VideoCaption extends Subview
initialize: ->
- @loaded = false
+ @loaded = false
bind: ->
$(window).bind('resize', @resize)
@@ -49,7 +49,7 @@ class @VideoCaption extends Subview
@$('.subtitles').html(container.html())
@$('.subtitles li[data-index]').click @seekPlayer
- # prepend and append an empty
for cosmatic reason
+ # prepend and append an empty
for cosmetic reason
@$('.subtitles').prepend($('
').height(@topSpacingHeight()))
.append($('
').height(@bottomSpacingHeight()))
@@ -131,10 +131,12 @@ class @VideoCaption extends Subview
toggle: (event) =>
event.preventDefault()
if @el.hasClass('closed')
+ $.cookie('hide_captions', 'false', expires: 3650, path: '/')
@$('.hide-subtitles').attr('title', 'Turn off captions')
@el.removeClass('closed')
@scrollCaption()
else
+ $.cookie('hide_captions', 'true', expires: 3650, path: '/')
@$('.hide-subtitles').attr('title', 'Turn on captions')
@el.addClass('closed')
From d893a14a531eb6c636ca2a3c56bcbdae14812a50 Mon Sep 17 00:00:00 2001
From: kimth
Date: Fri, 14 Sep 2012 17:24:15 -0400
Subject: [PATCH 07/82] Load player before turning off caption
---
common/lib/xmodule/xmodule/js/src/video/display.coffee | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/video/display.coffee b/common/lib/xmodule/xmodule/js/src/video/display.coffee
index 90e465d0ec..e226072b50 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display.coffee
@@ -10,11 +10,6 @@ class @Video
@fetchMetadata()
@parseSpeed()
- if $.cookie('hide_captions') == 'true'
- @el.addClass('closed')
- else
- @el.removeClass('closed')
-
$("#video_#{@id}").data('video', this).addClass('video-load-complete')
if YT.Player
@@ -24,6 +19,11 @@ class @Video
$('.course-content .video').each ->
$(this).data('video').embed()
+ if $.cookie('hide_captions') == 'true'
+ @el.addClass('closed')
+ else
+ @el.removeClass('closed')
+
youtubeId: (speed)->
@videos[speed || @speed]
From 1625125e6fa4c539fc5e973101f9a876adf19b37 Mon Sep 17 00:00:00 2001
From: kimth
Date: Sat, 15 Sep 2012 16:55:43 -0400
Subject: [PATCH 08/82] Respect object boundaries
---
common/lib/xmodule/xmodule/js/src/video/display.coffee | 7 ++-----
.../xmodule/js/src/video/display/video_player.coffee | 1 +
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/video/display.coffee b/common/lib/xmodule/xmodule/js/src/video/display.coffee
index e226072b50..2d7cb5fcfa 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display.coffee
@@ -12,6 +12,8 @@ class @Video
$("#video_#{@id}").data('video', this).addClass('video-load-complete')
+ @hide_captions = $.cookie('hide_captions') == 'true'
+
if YT.Player
@embed()
else
@@ -19,11 +21,6 @@ class @Video
$('.course-content .video').each ->
$(this).data('video').embed()
- if $.cookie('hide_captions') == 'true'
- @el.addClass('closed')
- else
- @el.removeClass('closed')
-
youtubeId: (speed)->
@videos[speed || @speed]
diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
index 4b265d20c8..79d27d2633 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
@@ -1,5 +1,6 @@
class @VideoPlayer extends Subview
initialize: ->
+ console.log(@['video'].hide_captions)
# Define a missing constant of Youtube API
YT.PlayerState.UNSTARTED = -1
From 439ca1dd59fce07c46ec450d45b6e4f6ed6df7dd Mon Sep 17 00:00:00 2001
From: kimth
Date: Sat, 15 Sep 2012 17:18:21 -0400
Subject: [PATCH 09/82] Write setter, toggle uses setter
---
.../js/src/video/display/video_caption.coffee | 19 ++++++++++++-------
.../js/src/video/display/video_player.coffee | 2 +-
2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee
index ae6b0c8ed1..b1e41afc3c 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display/video_caption.coffee
@@ -130,16 +130,21 @@ class @VideoCaption extends Subview
toggle: (event) =>
event.preventDefault()
- if @el.hasClass('closed')
- $.cookie('hide_captions', 'false', expires: 3650, path: '/')
+ if @el.hasClass('closed') # Captions are "closed" e.g. turned off
+ @hideCaptions(false)
+ else # Captions are on
+ @hideCaptions(true)
+
+ hideCaptions: (hide_captions) =>
+ if hide_captions
+ @$('.hide-subtitles').attr('title', 'Turn on captions')
+ @el.addClass('closed')
+ else
@$('.hide-subtitles').attr('title', 'Turn off captions')
@el.removeClass('closed')
@scrollCaption()
- else
- $.cookie('hide_captions', 'true', expires: 3650, path: '/')
- @$('.hide-subtitles').attr('title', 'Turn on captions')
- @el.addClass('closed')
-
+ $.cookie('hide_captions', hide_captions, expires: 3650, path: '/')
+
captionHeight: ->
if @el.hasClass('fullscreen')
$(window).height() - @$('.video-controls').height()
diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
index 79d27d2633..bb89def63d 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
@@ -1,6 +1,5 @@
class @VideoPlayer extends Subview
initialize: ->
- console.log(@['video'].hide_captions)
# Define a missing constant of Youtube API
YT.PlayerState.UNSTARTED = -1
@@ -46,6 +45,7 @@ class @VideoPlayer extends Subview
events:
onReady: @onReady
onStateChange: @onStateChange
+ @caption.hideCaptions(@['video'].hide_captions)
addToolTip: ->
@$('.add-fullscreen, .hide-subtitles').qtip
From 68e0701113616d03779974fc9b20bde4f7fc969a Mon Sep 17 00:00:00 2001
From: kimth
Date: Sat, 15 Sep 2012 17:23:41 -0400
Subject: [PATCH 10/82] Remove extra line
---
common/lib/xmodule/xmodule/js/src/video/display.coffee | 1 -
1 file changed, 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/js/src/video/display.coffee b/common/lib/xmodule/xmodule/js/src/video/display.coffee
index 2d7cb5fcfa..6587f05899 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display.coffee
@@ -9,7 +9,6 @@ class @Video
@parseVideos @el.data('streams')
@fetchMetadata()
@parseSpeed()
-
$("#video_#{@id}").data('video', this).addClass('video-load-complete')
@hide_captions = $.cookie('hide_captions') == 'true'
From 1a02cfec8350dd8bc98beeef70aaa632ea9b2021 Mon Sep 17 00:00:00 2001
From: kimth
Date: Sat, 15 Sep 2012 23:25:26 -0400
Subject: [PATCH 11/82] Reset CapaProblem when state HTML is corrupt
---
common/lib/xmodule/xmodule/capa_module.py | 36 ++++++++++++++++++-----
1 file changed, 29 insertions(+), 7 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 8bf1a56404..a344d2981d 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -131,17 +131,17 @@ class CapaModule(XModule):
self.weight = None
if self.rerandomize == 'never':
- seed = 1
+ self.seed = 1
elif self.rerandomize == "per_student" and hasattr(self.system, 'id'):
- seed = system.id
+ self.seed = system.id
else:
- seed = None
+ self.seed = None
try:
# TODO (vshnayder): move as much as possible of this work and error
# checking to descriptor load time
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
- instance_state, seed=seed, system=self.system)
+ instance_state, seed=self.seed, system=self.system)
except Exception as err:
msg = 'cannot create LoncapaProblem {loc}: {err}'.format(
loc=self.location.url(), err=err)
@@ -160,7 +160,7 @@ class CapaModule(XModule):
(self.location.url(), msg))
self.lcp = LoncapaProblem(
problem_text, self.location.html_id(),
- instance_state, seed=seed, system=self.system)
+ instance_state, seed=self.seed, system=self.system)
else:
# add extra info and raise
raise Exception(msg), None, sys.exc_info()[2]
@@ -220,9 +220,10 @@ class CapaModule(XModule):
try:
html = self.lcp.get_html()
except Exception, err:
+ log.exception(err)
+
# TODO (vshnayder): another switch on DEBUG.
if self.system.DEBUG:
- log.exception(err)
msg = (
'[courseware.capa.capa_module] '
'Failed to generate HTML for problem %s' %
@@ -231,7 +232,28 @@ class CapaModule(XModule):
msg += '
%s
' % traceback.format_exc().replace('<', '<')
html = msg
else:
- raise
+ # We're in non-debug mode, and possibly even in production. We want
+ # to avoid bricking of problem as much as possible
+
+ # Presumably, student submission has corrupted LoncapaProblem HTML.
+ # So, let's try generate a fresh LoncapaProblem
+ self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
+ state=None, # Tabula rasa
+ seed=self.seed, system=self.system)
+
+ # Prepend a scary warning to the student
+ warning = '
'
+ warning += '
Problem state was corruped by invalid input. '
+ warning += 'Problem reset to initial state! '
+ warning += 'If problem persists, please contact the course staff.
'
+ warning += '
'
+
+ html = warning
+ try:
+ html += self.lcp.get_html()
+ except Exception, err: # Couldn't do it. Give up
+ log.exception(err)
+ raise
content = {'name': self.display_name,
'html': html,
From 827e211ac75f59ff0c19373f6b31cde06e2f55c2 Mon Sep 17 00:00:00 2001
From: kimth
Date: Sun, 16 Sep 2012 00:27:41 -0400
Subject: [PATCH 12/82] Reveal student input at state of problem reset back to
student
---
common/lib/xmodule/xmodule/capa_module.py | 26 +++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index a344d2981d..894d23c342 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -1,3 +1,4 @@
+import cgi
import datetime
import dateutil
import dateutil.parser
@@ -236,16 +237,33 @@ class CapaModule(XModule):
# to avoid bricking of problem as much as possible
# Presumably, student submission has corrupted LoncapaProblem HTML.
- # So, let's try generate a fresh LoncapaProblem
+ # First, pull down all student answers
+ student_answers = self.lcp.student_answers
+ answer_ids = student_answers.keys()
+
+ # Some inputtypes, such as dynamath, have additional "hidden" state that
+ # is not exposed to the student. Keep those hidden
+ hidden_state_keywords = ['dynamath']
+ for answer_id in answer_ids:
+ for hidden_state_keyword in hidden_state_keywords:
+ if answer_id.find(hidden_state_keyword) >= 0:
+ student_answers.pop(answer_id)
+
+ # Next, generate a fresh LoncapaProblem
self.lcp = LoncapaProblem(self.definition['data'], self.location.html_id(),
state=None, # Tabula rasa
seed=self.seed, system=self.system)
# Prepend a scary warning to the student
warning = '
'
- warning += '
Problem state was corruped by invalid input. '
- warning += 'Problem reset to initial state! '
- warning += 'If problem persists, please contact the course staff.
'
+ warning += '
Problem reset to initial state!
'
+ warning += '
Problem state was corruped by invalid submission. The submission consisted of:
'
+ warning += '
'
+ for student_answer in student_answers.values():
+ if student_answer != '':
+ warning += '
' + cgi.escape(student_answer) + '
'
+ warning += '
'
+ warning += '
If problem persists, please contact the course staff.
'
warning += '
'
html = warning
From eac8037808c8c480ce3c34d6e39df06e21c2acdf Mon Sep 17 00:00:00 2001
From: kimth
Date: Sun, 16 Sep 2012 00:29:48 -0400
Subject: [PATCH 13/82] Add TODO comment
---
common/lib/xmodule/xmodule/capa_module.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 894d23c342..9eeaa57896 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -243,6 +243,7 @@ class CapaModule(XModule):
# Some inputtypes, such as dynamath, have additional "hidden" state that
# is not exposed to the student. Keep those hidden
+ # TODO: Use regex, e.g. 'dynamath' is suffix at end of answer_id
hidden_state_keywords = ['dynamath']
for answer_id in answer_ids:
for hidden_state_keyword in hidden_state_keywords:
From f5d6f080c7dc05e78dafe6062dcd20e9049393df Mon Sep 17 00:00:00 2001
From: kimth
Date: Sun, 16 Sep 2012 00:47:40 -0400
Subject: [PATCH 14/82] Add basic styles
---
common/lib/xmodule/xmodule/capa_module.py | 14 +++++++-------
common/lib/xmodule/xmodule/css/capa/display.scss | 16 ++++++++++++++++
2 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 9eeaa57896..f6ba0d278d 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -256,16 +256,16 @@ class CapaModule(XModule):
seed=self.seed, system=self.system)
# Prepend a scary warning to the student
- warning = '
'
- warning += '
Problem reset to initial state!
'
- warning += '
Problem state was corruped by invalid submission. The submission consisted of:
'
- warning += '
'
+ warning = '
'\
+ '
Warning: Problem reset to initial state!
'\
+ 'Problem state was corruped by invalid submission. The submission consisted of:'\
+ '
'
for student_answer in student_answers.values():
if student_answer != '':
warning += '
' + cgi.escape(student_answer) + '
'
- warning += '
'
- warning += '
If problem persists, please contact the course staff.
'
- warning += '
'
+ warning += '
'\
+ 'If problem persists, please contact the course staff.'\
+ '
'
warning += ''\
- 'If problem persists, please contact the course staff.'\
+ 'If the problem persists, please contact the course staff.'\
''
html = warning
From 7682663e1b90a1566bab37987df02b817863415a Mon Sep 17 00:00:00 2001
From: Arjun Singh
Date: Sat, 15 Sep 2012 22:07:10 -0700
Subject: [PATCH 16/82] Make broken problem error a bit scarier and fix typo in
message
---
common/lib/xmodule/xmodule/capa_module.py | 7 ++++---
common/lib/xmodule/xmodule/css/capa/display.scss | 6 +++---
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 17b5e2921a..a891474581 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -257,14 +257,15 @@ class CapaModule(XModule):
# Prepend a scary warning to the student
warning = '
'\
- '
Warning: Problem reset to initial state!
'\
- 'Problem state was corruped by invalid submission. The submission consisted of:'\
+ '
Warning: The problem has been reset to its initial state!
'\
+ 'The problem\'s state was corrupted by an invalid submission. ' \
+ 'The submission consisted of:'\
'
'
for student_answer in student_answers.values():
if student_answer != '':
warning += '
' + cgi.escape(student_answer) + '
'
warning += '
'\
- 'If the problem persists, please contact the course staff.'\
+ 'If this error persists, please contact the course staff.'\
'
'
html = warning
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 944904ca54..f349de31b5 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -467,15 +467,15 @@ section.problem {
div.capa_reset {
padding: 25px;
- border: 1px solid #EBE8BF;
+ border: 1px solid $error-red;
+ background-color: lighten($error-red, 25%);
border-radius: 3px;
- background: #FFFCDD;
font-size: 1em;
margin-top: 10px;
margin-bottom: 10px;
}
.capa_reset>h2 {
- color: #FF0000;
+ color: #AA0000;
}
.capa_reset li {
font-size: 0.9em;
From 44d5bfa9d0a41e586543eef1c33e073298fb4cc0 Mon Sep 17 00:00:00 2001
From: kimth
Date: Sun, 16 Sep 2012 10:27:37 -0400
Subject: [PATCH 17/82] Trivial fix to detailed-solution style
---
common/lib/xmodule/xmodule/css/capa/display.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index f349de31b5..3e90b3790c 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -437,7 +437,7 @@ section.problem {
.detailed-solution {
border: 1px solid #ddd;
padding: 9px 15px 20px;
- margin-bottom: 10px;
+ margin: 20px 10px;
background: #FFF;
position: relative;
@include box-shadow(inset 0 0 0 1px #eee);
From 1dfd222b9850dccf90cebd8434a8f8ef95df1503 Mon Sep 17 00:00:00 2001
From: kimth
Date: Sun, 16 Sep 2012 14:50:40 -0400
Subject: [PATCH 18/82] Escape quotations, lt/gt, ampersand
---
common/lib/capa/capa/inputtypes.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py
index 187d2fd422..9ae63fb43a 100644
--- a/common/lib/capa/capa/inputtypes.py
+++ b/common/lib/capa/capa/inputtypes.py
@@ -333,6 +333,10 @@ def textline_dynamath(element, value, status, render_template, msg=''):
if '' in preprocessor.values():
preprocessor = None
+ # Escape characters in student input for safe XML parsing
+ escapedict = {'"': '"'}
+ value = saxutils.escape(value, escapedict)
+
context = {'id': eid, 'value': value, 'state': status, 'count': count, 'size': size,
'msg': msg, 'hidden': hidden,
'preprocessor': preprocessor,
From 9e01b51f968387b814441672463b31f93d7da3df Mon Sep 17 00:00:00 2001
From: kimth
Date: Sun, 16 Sep 2012 16:46:19 -0400
Subject: [PATCH 19/82] Tiny fix to style
---
common/lib/xmodule/xmodule/css/capa/display.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 3e90b3790c..5f918ff563 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -443,7 +443,7 @@ section.problem {
@include box-shadow(inset 0 0 0 1px #eee);
@include border-radius(3px);
- p:first-child {
+ > p:first-child {
font-size: 0.9em;
font-weight: bold;
font-style: normal;
From 2133ec9686ba41c3d725e7d79276b6a8050e561d Mon Sep 17 00:00:00 2001
From: Tom Giannattasio
Date: Mon, 17 Sep 2012 11:55:37 -0400
Subject: [PATCH 20/82] fixed word wrap issue on discussions
---
lms/static/sass/_discussion.scss | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss
index 78bda06442..c97972333f 100644
--- a/lms/static/sass/_discussion.scss
+++ b/lms/static/sass/_discussion.scss
@@ -1260,6 +1260,10 @@ body.discussion {
padding: 40px;
min-height: 468px;
+ a {
+ word-wrap: break-word;
+ }
+
h1 {
margin-bottom: 10px;
font-size: 28px;
From d016c0b20aad6651256e4cfc4b504daf75e831a4 Mon Sep 17 00:00:00 2001
From: Tom Giannattasio
Date: Mon, 17 Sep 2012 12:00:08 -0400
Subject: [PATCH 21/82] fixed discussion search cursor bug
---
lms/static/sass/_discussion.scss | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss
index c97972333f..79c853f42d 100644
--- a/lms/static/sass/_discussion.scss
+++ b/lms/static/sass/_discussion.scss
@@ -940,7 +940,7 @@ body.discussion {
display: block;
width: 100%;
height: 30px;
- padding: 0;
+ padding: 0 0 0 30px;
margin: 14px auto;
@include box-sizing(border-box);
border: 1px solid #acacac;
@@ -951,7 +951,6 @@ body.discussion {
font-weight: 400;
font-size: 13px;
line-height: 20px;
- text-indent: 30px;
color: #333;
outline: 0;
cursor: pointer;
From 261948f38a7709e1972d5a140f2fd6b7470d3351 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 17 Sep 2012 13:19:24 -0400
Subject: [PATCH 22/82] Make textbooks in the course definition be stored as
data, rather than objects, and turn them into objects during module
instatiation
---
common/lib/xmodule/xmodule/course_module.py | 28 +++++++++------------
1 file changed, 12 insertions(+), 16 deletions(-)
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 7aa904205d..5ea0c13d65 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -22,9 +22,6 @@ class CourseDescriptor(SequenceDescriptor):
self.book_url = book_url
self.table_of_contents = self._get_toc_from_s3()
- @classmethod
- def from_xml_object(cls, xml_object):
- return cls(xml_object.get('title'), xml_object.get('book_url'))
@property
def table_of_contents(self):
@@ -57,10 +54,18 @@ class CourseDescriptor(SequenceDescriptor):
return table_of_contents
-
def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
- self.textbooks = self.definition['data']['textbooks']
+
+ self.textbooks = []
+ for title, book_url in self.definition['data']['textbooks']:
+ try:
+ self.textbooks.append(self.Textbook(title, book_url))
+ except:
+ # If we can't get to S3 (e.g. on a train with no internet), don't break
+ # the rest of the courseware.
+ log.exception("Couldn't load textbook ({0}, {1})".format(title, book_url))
+ continue
self.wiki_slug = self.definition['data']['wiki_slug'] or self.location.course
@@ -82,7 +87,6 @@ class CourseDescriptor(SequenceDescriptor):
# disable the syllabus content for courses that do not provide a syllabus
self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))
-
def set_grading_policy(self, policy_str):
"""Parse the policy specified in policy_str, and save it"""
try:
@@ -94,19 +98,11 @@ class CourseDescriptor(SequenceDescriptor):
# the error log.
self._grading_policy = {}
-
@classmethod
def definition_from_xml(cls, xml_object, system):
textbooks = []
for textbook in xml_object.findall("textbook"):
- try:
- txt = cls.Textbook.from_xml_object(textbook)
- except:
- # If we can't get to S3 (e.g. on a train with no internet), don't break
- # the rest of the courseware.
- log.exception("Couldn't load textbook")
- continue
- textbooks.append(txt)
+ textbooks = (textbook.get('title'), textbook.get('book_url'))
xml_object.remove(textbook)
#Load the wiki tag if it exists
@@ -116,7 +112,7 @@ class CourseDescriptor(SequenceDescriptor):
wiki_slug = wiki_tag.attrib.get("slug", default=None)
xml_object.remove(wiki_tag)
- definition = super(CourseDescriptor, cls).definition_from_xml(xml_object, system)
+ definition = super(CourseDescriptor, cls).definition_from_xml(xml_object, system)
definition.setdefault('data', {})['textbooks'] = textbooks
definition['data']['wiki_slug'] = wiki_slug
From 34541b5e7c4b597b5f87231345640c64602398cb Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 17 Sep 2012 13:49:42 -0400
Subject: [PATCH 23/82] Make a list of textbooks, don't just store a single one
---
common/lib/xmodule/xmodule/course_module.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 5ea0c13d65..3af65d1bd6 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -102,7 +102,7 @@ class CourseDescriptor(SequenceDescriptor):
def definition_from_xml(cls, xml_object, system):
textbooks = []
for textbook in xml_object.findall("textbook"):
- textbooks = (textbook.get('title'), textbook.get('book_url'))
+ textbooks.append((textbook.get('title'), textbook.get('book_url')))
xml_object.remove(textbook)
#Load the wiki tag if it exists
From 0a55e1eae13789cc3cad1f693cfc072351f44af5 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 17 Sep 2012 13:51:33 -0400
Subject: [PATCH 24/82] Add sub-environments for the lms that is running
alongside the lms for debugging
---
lms/envs/cms/__init__.py | 0
lms/envs/{with_cms.py => cms/aws.py} | 0
lms/envs/cms/dev.py | 19 +++++++++++++++++++
rakefile | 4 ++--
4 files changed, 21 insertions(+), 2 deletions(-)
create mode 100644 lms/envs/cms/__init__.py
rename lms/envs/{with_cms.py => cms/aws.py} (100%)
create mode 100644 lms/envs/cms/dev.py
diff --git a/lms/envs/cms/__init__.py b/lms/envs/cms/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lms/envs/with_cms.py b/lms/envs/cms/aws.py
similarity index 100%
rename from lms/envs/with_cms.py
rename to lms/envs/cms/aws.py
diff --git a/lms/envs/cms/dev.py b/lms/envs/cms/dev.py
new file mode 100644
index 0000000000..6e4697cccb
--- /dev/null
+++ b/lms/envs/cms/dev.py
@@ -0,0 +1,19 @@
+"""
+Settings for the LMS that runs alongside the CMS on AWS
+"""
+
+from ..dev import *
+
+MODULESTORE = {
+ 'default': {
+ 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
+ 'OPTIONS': {
+ 'default_class': 'xmodule.raw_module.RawDescriptor',
+ 'host': 'localhost',
+ 'db': 'xmodule',
+ 'collection': 'modulestore',
+ 'fs_root': DATA_DIR,
+ 'render_template': 'mitxmako.shortcuts.render_to_string',
+ }
+ }
+}
diff --git a/rakefile b/rakefile
index 9e0bbcbfa4..be5ef1d9e7 100644
--- a/rakefile
+++ b/rakefile
@@ -125,8 +125,8 @@ TEST_TASKS = []
end
# Per environment tasks
- Dir["#{system}/envs/*.py"].each do |env_file|
- env = File.basename(env_file).gsub(/\.py/, '')
+ Dir["#{system}/envs/**/*.py"].each do |env_file|
+ env = env_file.gsub("#{system}/envs/", '').gsub(/\.py/, '').gsub('/', '.')
desc "Attempt to import the settings file #{system}.envs.#{env} and report any errors"
task "#{system}:check_settings:#{env}" => :predjango do
sh("echo 'import #{system}.envs.#{env}' | #{django_admin(system, env, 'shell')}")
From d17e2aada90f69e30940345037f87d76d3c1b033 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 17 Sep 2012 14:19:07 -0400
Subject: [PATCH 25/82] Remove trailing '/' from ajax_urls
---
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 d701db33a3..505b897497 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -207,7 +207,7 @@ def preview_module_system(request, preview_id, descriptor):
descriptor: An XModuleDescriptor
"""
return ModuleSystem(
- ajax_url=reverse('preview_dispatch', args=[preview_id, descriptor.location.url(), '']),
+ ajax_url=reverse('preview_dispatch', args=[preview_id, descriptor.location.url(), '']).rstrip('/'),
# TODO (cpennington): Do we want to track how instructors are using the preview problems?
track_function=lambda type, event: None,
filestore=descriptor.system.resources_fs,
From c0ed79397647f90458fb80a7552f1980fd2679d3 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 17 Sep 2012 14:19:23 -0400
Subject: [PATCH 26/82] Add an empty get_errored_courses function for the mongo
modulestore
---
common/lib/xmodule/xmodule/modulestore/mongo.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index 7aa05e474f..33901947a6 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -316,3 +316,9 @@ class MongoModuleStore(ModuleStoreBase):
{'_id': True})
return [i['_id'] for i in items]
+ def get_errored_courses(self):
+ """
+ This function doesn't make sense for the mongo modulestore, as courses
+ are loaded on demand, rather than up front
+ """
+ return {}
From 53ec558f115f80eb2bb654ddfdd251de24cf333e Mon Sep 17 00:00:00 2001
From: Tom Giannattasio
Date: Mon, 17 Sep 2012 14:38:19 -0400
Subject: [PATCH 27/82] fixed solution set glitch
---
common/lib/xmodule/xmodule/css/capa/display.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 8a6664b08f..4302e0b92e 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -68,7 +68,7 @@ section.problem {
}
.solution-span {
- span {
+ > span {
margin: 20px 0;
display: block;
border: 1px solid #ddd;
From 3bf8ae8f090a7d26c4840c6d8316f7709c11f706 Mon Sep 17 00:00:00 2001
From: Tom Giannattasio
Date: Mon, 17 Sep 2012 14:44:32 -0400
Subject: [PATCH 28/82] double border fix
---
common/lib/xmodule/xmodule/css/capa/display.scss | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 4302e0b92e..8ed67ff20e 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -67,8 +67,7 @@ section.problem {
}
}
- .solution-span {
- > span {
+ span.solution-span {
margin: 20px 0;
display: block;
border: 1px solid #ddd;
@@ -81,7 +80,6 @@ section.problem {
&:empty {
display: none;
}
- }
}
div {
From 0acc75e3a4f1bb8f7f753d33cf92f425086a576e Mon Sep 17 00:00:00 2001
From: Tom Giannattasio
Date: Mon, 17 Sep 2012 14:51:20 -0400
Subject: [PATCH 29/82] fixed detailed-solution bug
---
.../lib/xmodule/xmodule/css/capa/display.scss | 32 ++++++++-----------
1 file changed, 13 insertions(+), 19 deletions(-)
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index 8ed67ff20e..d7ffa198af 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -67,18 +67,20 @@ section.problem {
}
}
- span.solution-span {
- margin: 20px 0;
- display: block;
- border: 1px solid #ddd;
- padding: 9px 15px 20px;
- background: #FFF;
- position: relative;
- @include box-shadow(inset 0 0 0 1px #eee);
- @include border-radius(3px);
+ .solution-span {
+ > span {
+ margin: 20px 0;
+ display: block;
+ border: 1px solid #ddd;
+ padding: 9px 15px 20px;
+ background: #FFF;
+ position: relative;
+ @include box-shadow(inset 0 0 0 1px #eee);
+ @include border-radius(3px);
- &:empty {
- display: none;
+ &:empty {
+ display: none;
+ }
}
}
@@ -478,14 +480,6 @@ section.problem {
}
.detailed-solution {
- border: 1px solid #ddd;
- padding: 9px 15px 20px;
- margin: 20px 10px;
- background: #FFF;
- position: relative;
- @include box-shadow(inset 0 0 0 1px #eee);
- @include border-radius(3px);
-
> p:first-child {
font-size: 0.9em;
font-weight: bold;
From abf2e508939d808f714375814dc1407a9b8fecf8 Mon Sep 17 00:00:00 2001
From: John Jarvis
Date: Mon, 17 Sep 2012 15:15:15 -0400
Subject: [PATCH 30/82] homebrew changed the installation url
---
create-dev-env.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/create-dev-env.sh b/create-dev-env.sh
index 3664129775..d28a5891d9 100755
--- a/create-dev-env.sh
+++ b/create-dev-env.sh
@@ -223,7 +223,7 @@ EO
command -v brew &>/dev/null || {
output "Installing brew"
- /usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/master/Library/Contributions/install_homebrew.rb)"
+ /usr/bin/ruby <(curl -fsSkL raw.github.com/mxcl/homebrew/go)
}
command -v git &>/dev/null || {
output "Installing git"
From f45fa578a26c6274e21391c6698be9ff7e1c356d Mon Sep 17 00:00:00 2001
From: kimth
Date: Mon, 17 Sep 2012 15:56:09 -0400
Subject: [PATCH 31/82] onreset rerandomize option
---
common/lib/xmodule/xmodule/capa_module.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index a891474581..0d810af87a 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -179,6 +179,8 @@ class CapaModule(XModule):
return "per_student"
elif rerandomize == "never":
return "never"
+ elif rerandomize == "onreset":
+ return "onreset"
else:
raise Exception("Invalid rerandomize attribute " + rerandomize)
@@ -307,7 +309,7 @@ class CapaModule(XModule):
save_button = False
# Only show the reset button if pressing it will show different values
- if self.rerandomize != 'always':
+ if self.rerandomize not in ["always", "onreset"]:
reset_button = False
# User hasn't submitted an answer yet -- we don't want resets
@@ -617,7 +619,7 @@ class CapaModule(XModule):
return "Refresh the page and make an attempt before resetting."
self.lcp.do_reset()
- if self.rerandomize == "always":
+ if self.rerandomize in ["always", "onreset"]:
# reset random number generator seed (note the self.lcp.get_state()
# in next line)
self.lcp.seed = None
From 90f37b3344274db67cfc118f0f377c09227f5cdc Mon Sep 17 00:00:00 2001
From: Arjun Singh
Date: Mon, 17 Sep 2012 14:10:28 -0700
Subject: [PATCH 32/82] Don't use the search API to retrieve all threads.
---
.../django_comment_client/forum/views.py | 20 -------------------
.../views/discussion_thread_list_view.coffee | 15 +++++++++++++-
lms/templates/discussion/_accordion.html | 0
3 files changed, 14 insertions(+), 21 deletions(-)
delete mode 100644 lms/templates/discussion/_accordion.html
diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py
index 573badbb92..0abcbf5fbb 100644
--- a/lms/djangoapps/django_comment_client/forum/views.py
+++ b/lms/djangoapps/django_comment_client/forum/views.py
@@ -28,26 +28,6 @@ PAGES_NEARBY_DELTA = 2
escapedict = {'"': '"'}
log = logging.getLogger("edx.discussions")
-def _general_discussion_id(course_id):
- return course_id.replace('/', '_').replace('.', '_')
-
-def _should_perform_search(request):
- return bool(request.GET.get('text', False) or \
- request.GET.get('tags', False))
-
-def render_accordion(request, course, discussion_id):
- # TODO: Delete if obsolete
- discussion_info = utils.get_categorized_discussion_info(request, course)
-
- context = {
- 'course': course,
- 'discussion_info': discussion_info,
- 'active': discussion_id,
- 'csrf': csrf(request)['csrf_token'],
- }
-
- return render_to_string('discussion/_accordion.html', context)
-
def get_threads(request, course_id, discussion_id=None, per_page=THREADS_PER_PAGE):
"""
This may raise cc.utils.CommentClientError or
diff --git a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
index d4c144f808..6b8abbdc91 100644
--- a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -244,7 +244,8 @@ if Backbone?
item = $(event.target).closest('li')
if item.find("span.board-name").data("discussion_id") == "#all"
@discussionIds = ""
- @clearSearch()
+ @$(".post-search-field").val("")
+ @retrieveAllThreads()
else
discussionIds = _.map item.find(".board-name[data-discussion_id]"), (board) -> $(board).data("discussion_id").id
@retrieveDiscussions(discussionIds)
@@ -277,6 +278,18 @@ if Backbone?
Content.loadContentInfos(response.content_info)
@displayedCollection.reset(@collection.models)
+ retrieveAllThreads: () ->
+ url = DiscussionUtil.urlFor("threads")
+ DiscussionUtil.safeAjax
+ url: url
+ type: "GET"
+ success: (response, textStatus) =>
+ @collection.current_page = response.page
+ @collection.pages = response.num_pages
+ @collection.reset(response.discussion_data)
+ Content.loadContentInfos(response.content_info)
+ @displayedCollection.reset(@collection.models)
+
sortThreads: (event) ->
@$(".sort-bar a").removeClass("active")
$(event.target).addClass("active")
diff --git a/lms/templates/discussion/_accordion.html b/lms/templates/discussion/_accordion.html
deleted file mode 100644
index e69de29bb2..0000000000
From aaa4db8d299a2bfb4678994f2d493fa35b711eae Mon Sep 17 00:00:00 2001
From: Arjun Singh
Date: Mon, 17 Sep 2012 15:54:24 -0700
Subject: [PATCH 33/82] Fixes image uploades in firefox
---
lms/static/js/jquery.ajaxfileupload.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lms/static/js/jquery.ajaxfileupload.js b/lms/static/js/jquery.ajaxfileupload.js
index d761c5ae3c..e588d3555a 100644
--- a/lms/static/js/jquery.ajaxfileupload.js
+++ b/lms/static/js/jquery.ajaxfileupload.js
@@ -79,7 +79,7 @@ jQuery.extend({
try {
if(io.contentWindow){
xml.responseText = io.contentWindow.document.body ?
- io.contentWindow.document.body.innerText : null;
+ io.contentWindow.document.body.textContent || io.contentWindow.document.body.innerText : null;
xml.responseXML = io.contentWindow.document.XMLDocument ?
io.contentWindow.document.XMLDocument : io.contentWindow.document;
From 0fdd0a005fdfab2679bb9fca5b11161d4c1dce1c Mon Sep 17 00:00:00 2001
From: kimth
Date: Mon, 17 Sep 2012 20:40:06 -0400
Subject: [PATCH 34/82] Add docs to xml_format
---
doc/xml-format.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/xml-format.md b/doc/xml-format.md
index 29c60fea99..0dd2c59417 100644
--- a/doc/xml-format.md
+++ b/doc/xml-format.md
@@ -274,6 +274,7 @@ __Inherited:__
* `showanswer` - When to show answer. For 'attempted', will show answer after first attempt. Values: never, attempted, answered, closed. Default: closed. Optional.
* `graded` - Whether this section will count towards the students grade. "true" or "false". Defaults to "false".
* `rerandomise` - Randomize question on each attempt. Values: 'always' (students see a different version of the problem after each attempt to solve it)
+ 'onreset' (students see a different version of the problem when they reset, but are not forced to reset after each check)
'never' (all students see the same version of the problem)
'per_student' (individual students see the same version of the problem each time the look at it, but that version is different from what other students see)
Default: 'always'. Optional.
From 5d4c60a880c9877615a4839b574995b9e8e0c000 Mon Sep 17 00:00:00 2001
From: Ibrahim Awwal
Date: Mon, 17 Sep 2012 16:20:00 -0700
Subject: [PATCH 35/82] Move new post button handler to DiscussionRouter.
---
.../coffee/src/discussion/discussion_router.coffee | 10 ++++++++++
lms/static/js/discussions-temp.js | 11 -----------
2 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/lms/static/coffee/src/discussion/discussion_router.coffee b/lms/static/coffee/src/discussion/discussion_router.coffee
index b2e41cd5af..a5219a68b9 100644
--- a/lms/static/coffee/src/discussion/discussion_router.coffee
+++ b/lms/static/coffee/src/discussion/discussion_router.coffee
@@ -14,6 +14,9 @@ if Backbone?
@newPostView = new NewPostView(el: $(".new-post-article"), collection: @discussion)
@nav.on "thread:created", @navigateToThread
+ @newPost = $('.new-post-article')
+ $('.new-post-btn').bind "click", @showNewPost
+ $('.new-post-cancel').bind "click", @hideNewPost
allThreads: ->
@nav.updateSidebar()
@@ -43,3 +46,10 @@ if Backbone?
navigateToAllThreads: =>
@navigate("", trigger: true)
+
+ showNewPost: (event) =>
+ @newPost.slideDown(300)
+ $('.new-post-title').focus()
+
+ hideNewPost: (event) =>
+ @newPost.slideUp(300)
diff --git a/lms/static/js/discussions-temp.js b/lms/static/js/discussions-temp.js
index dd6af6ef14..ec7c189938 100644
--- a/lms/static/js/discussions-temp.js
+++ b/lms/static/js/discussions-temp.js
@@ -55,8 +55,6 @@ $(document).ready(function() {
// $topicDrop.bind('click', setTopic);
$formTopicDropBtn.bind('click', showFormTopicDrop);
$formTopicDropMenu.bind('click', setFormTopic);
- $('.new-post-btn').bind('click', newPost);
- $('.new-post-cancel').bind('click', closeNewPost);
$body.delegate('[data-tooltip]', {
'mouseover': showTooltip,
@@ -266,15 +264,6 @@ function setTopic(e) {
showBrowse();
}
-function newPost(e) {
- $newPost.slideDown(300);
- $('.new-post-title').focus();
-}
-
-function closeNewPost(e) {
- $newPost.slideUp(300);
-}
-
function showFormTopicDrop(e) {
$formTopicDropBtn.addClass('is-dropped');
$formTopicDropMenu.show();
From 680e681feef421bc6de0cc41c8754c2a24c57c31 Mon Sep 17 00:00:00 2001
From: Ibrahim Awwal
Date: Mon, 17 Sep 2012 17:13:06 -0700
Subject: [PATCH 36/82] Removed a lot of dead code and moved tooltips into
their own file.
---
.../src/discussion/views/new_post_view.coffee | 3 -
lms/static/js/discussions-temp.js | 222 +-----------------
lms/static/js/tooltips.js | 56 +++++
.../discussion/_js_head_dependencies.html | 1 +
4 files changed, 61 insertions(+), 221 deletions(-)
create mode 100644 lms/static/js/tooltips.js
diff --git a/lms/static/coffee/src/discussion/views/new_post_view.coffee b/lms/static/coffee/src/discussion/views/new_post_view.coffee
index 2400a3b18e..ac5d43b60d 100644
--- a/lms/static/coffee/src/discussion/views/new_post_view.coffee
+++ b/lms/static/coffee/src/discussion/views/new_post_view.coffee
@@ -120,9 +120,6 @@ if Backbone?
anonymous_to_peers = false || @$("input.discussion-anonymous-to-peers").is(":checked")
follow = false || @$("input.discussion-follow").is(":checked")
- $formTopicDropBtn.bind('click', showFormTopicDrop)
- $formTopicDropMenu.bind('click', setFormTopic)
-
url = DiscussionUtil.urlFor('create_thread', @topicId)
DiscussionUtil.safeAjax
diff --git a/lms/static/js/discussions-temp.js b/lms/static/js/discussions-temp.js
index ec7c189938..9ca1a9ba8f 100644
--- a/lms/static/js/discussions-temp.js
+++ b/lms/static/js/discussions-temp.js
@@ -2,30 +2,16 @@ var $body;
var $browse;
var $search;
var $searchField;
-var $topicDrop;
var $currentBoard;
-var $tooltip;
+
var $newPost;
-var $thread;
var $sidebar;
var $sidebarWidthStyles;
-var $formTopicDropBtn;
-var $formTopicDropMenu;
var $postListWrapper;
-var $dropFilter;
-var $topicFilter;
var $discussionBody;
var sidebarWidth;
-var sidebarHeight;
-var sidebarHeaderHeight;
var sidebarXOffset;
var scrollTop;
-var discussionsBodyTop;
-var discussionsBodyBottom;
-var tooltipTimer;
-var tooltipCoords;
-var SIDEBAR_PADDING = 10;
-var SIDEBAR_HEADER_HEIGHT = 87;
$(document).ready(function() {
@@ -35,13 +21,11 @@ $(document).ready(function() {
$searchField = $('.post-search-field');
//$topicDrop = $('.browse-topic-drop-menu-wrapper');
$currentBoard = $('.current-board');
- $tooltip = $('');
+
$newPost = $('.new-post-article');
$sidebar = $('.sidebar');
$discussionBody = $('.discussion-body');
$postListWrapper = $('.post-list-wrapper');
- $formTopicDropBtn = $('.new-post-article .form-topic-drop-btn');
- $formTopicDropMenu = $('.new-post-article .form-topic-drop-menu-wrapper');
// $dropFilter = $('.browse-topic-drop-search-input');
// $topicFilter = $('.topic-drop-search-input');
$sidebarWidthStyles = $('');
@@ -53,15 +37,8 @@ $(document).ready(function() {
//$browse.bind('click', showTopicDrop);
//$search.bind('click', showSearch);
// $topicDrop.bind('click', setTopic);
- $formTopicDropBtn.bind('click', showFormTopicDrop);
- $formTopicDropMenu.bind('click', setFormTopic);
-
- $body.delegate('[data-tooltip]', {
- 'mouseover': showTooltip,
- 'mousemove': moveTooltip,
- 'mouseout': hideTooltip,
- 'click': hideTooltip
- });
+// $formTopicDropBtn.bind('click', showFormTopicDrop);
+// $formTopicDropMenu.bind('click', setFormTopic);
$body.delegate('.browse-topic-drop-search-input, .form-topic-drop-search-input', 'keyup', filterDrop);
});
@@ -112,7 +89,6 @@ function filterDrop(e) {
/*
* single query
*/
-
var $drop = $(e.target).parents('.topic_menu_wrapper, .browse-topic-drop-menu-wrapper');
var query = $(this).val();
var $items = $drop.find('a');
@@ -147,193 +123,3 @@ function filterDrop(e) {
}
});
}
-
-function showTooltip(e) {
- var tooltipText = $(this).attr('data-tooltip');
- $tooltip.html(tooltipText);
- $body.append($tooltip);
- $(this).children().css('pointer-events', 'none');
-
- tooltipCoords = {
- x: e.pageX - ($tooltip.outerWidth() / 2),
- y: e.pageY - ($tooltip.outerHeight() + 15)
- };
-
- $tooltip.css({
- 'left': tooltipCoords.x,
- 'top': tooltipCoords.y
- });
-
- tooltipTimer = setTimeout(function() {
- $tooltip.show().css('opacity', 1);
-
- tooltipTimer = setTimeout(function() {
- hideTooltip();
- }, 3000);
- }, 500);
-}
-
-function moveTooltip(e) {
- tooltipCoords = {
- x: e.pageX - ($tooltip.outerWidth() / 2),
- y: e.pageY - ($tooltip.outerHeight() + 15)
- };
-
- $tooltip.css({
- 'left': tooltipCoords.x,
- 'top': tooltipCoords.y
- });
-}
-
-function hideTooltip(e) {
- $tooltip.hide().css('opacity', 0);
- clearTimeout(tooltipTimer);
-}
-
-function showBrowse(e) {
- $browse.addClass('is-open');
- $search.removeClass('is-open');
- $searchField.val('');
-}
-
-function showSearch(e) {
- $search.addClass('is-open');
- $browse.removeClass('is-open');
- setTimeout(function() {
- $searchField.focus();
- }, 200);
-}
-
-function showTopicDrop(e) {
- e.preventDefault();
-
- $browse.addClass('is-dropped');
-
- if(!$topicDrop[0]) {
- $topicDrop = $('.browse-topic-drop-menu-wrapper');
- }
-
- $topicDrop.show();
- $browse.unbind('click', showTopicDrop);
- $body.bind('keyup', setActiveDropItem);
- $browse.bind('click', hideTopicDrop);
- setTimeout(function() {
- $body.bind('click', hideTopicDrop);
- }, 0);
-}
-
-function hideTopicDrop(e) {
- if(e.target == $('.browse-topic-drop-search-input')[0]) {
- return;
- }
-
- $browse.removeClass('is-dropped');
- $topicDrop.hide();
- $body.unbind('click', hideTopicDrop);
- $browse.bind('click', showTopicDrop);
-}
-
-function setTopic(e) {
- if(e.target == $('.browse-topic-drop-search-input')[0]) {
- return;
- }
-
- var $item = $(e.target).closest('a');
- var boardName = $item.find('.board-name').html();
-
- $item.parents('ul').not('.browse-topic-drop-menu').each(function(i) {
- boardName = $(this).siblings('a').find('.board-name').html() + ' / ' + boardName;
- });
-
- if(!$currentBoard[0]) {
- $currentBoard = $('.current-board');
- }
- $currentBoard.html(boardName);
-
- var fontSize = 16;
- $currentBoard.css('font-size', '16px');
-
- while($currentBoard.width() > (sidebarWidth * .8) - 40) {
- fontSize--;
- if(fontSize < 11) {
- break;
- }
- $currentBoard.css('font-size', fontSize + 'px');
- }
-
- showBrowse();
-}
-
-function showFormTopicDrop(e) {
- $formTopicDropBtn.addClass('is-dropped');
- $formTopicDropMenu.show();
- $formTopicDropBtn.unbind('click', showFormTopicDrop);
- $formTopicDropBtn.bind('click', hideFormTopicDrop);
-
- setTimeout(function() {
- $body.bind('click', hideFormTopicDrop);
- }, 0);
-
-}
-
-function hideFormTopicDrop(e) {
- if(e.target == $('.topic-drop-search-input')[0]) {
- return;
- }
-
- $formTopicDropBtn.removeClass('is-dropped');
- $formTopicDropMenu.hide();
- $body.unbind('click', hideFormTopicDrop);
- $formTopicDropBtn.unbind('click', hideFormTopicDrop);
- $formTopicDropBtn.bind('click', showFormTopicDrop);
-}
-
-function setFormTopic(e) {
- if(e.target == $('.topic-drop-search-input')[0]) {
- return;
- }
- $formTopicDropBtn.removeClass('is-dropped');
- hideFormTopicDrop(e);
-
- var $item = $(e.target);
- var boardName = $item.html();
- $item.parents('ul').not('.form-topic-drop-menu').each(function(i) {
- boardName = $(this).siblings('a').html() + ' / ' + boardName;
- });
- $formTopicDropBtn.html(boardName + ' ▾');
-}
-
-function updateSidebar(e) {
- // determine page scroll attributes
- scrollTop = $(window).scrollTop();
- discussionsBodyTop = $discussionBody.offset().top;
- discussionsBodyBottom = discussionsBodyTop + $discussionBody.height();
- var windowHeight = $(window).height();
-
- // toggle fixed positioning
- if(scrollTop > discussionsBodyTop - SIDEBAR_PADDING) {
- $sidebar.addClass('fixed');
- $sidebar.css('top', SIDEBAR_PADDING + 'px');
- } else {
- $sidebar.removeClass('fixed');
- $sidebar.css('top', '0');
- }
-
- // set sidebar width
- var sidebarWidth = .32 * $discussionBody.width() - 10;
- $sidebar.css('width', sidebarWidth + 'px');
-
- // show the entire sidebar at all times
- var sidebarHeight = windowHeight - (scrollTop < discussionsBodyTop - SIDEBAR_PADDING ? discussionsBodyTop - scrollTop : SIDEBAR_PADDING) - SIDEBAR_PADDING - (scrollTop + windowHeight > discussionsBodyBottom + SIDEBAR_PADDING ? scrollTop + windowHeight - discussionsBodyBottom - SIDEBAR_PADDING : 0);
- $sidebar.css('height', sidebarHeight > 400 ? sidebarHeight : 400 + 'px');
-
- // update the list height
- if(!$postListWrapper[0]) {
- $postListWrapper = $('.post-list-wrapper');
- }
- $postListWrapper.css('height', (sidebarHeight - SIDEBAR_HEADER_HEIGHT - 4) + 'px');
-
- // update title wrappers
- var titleWidth = sidebarWidth - 115;
- $sidebarWidthStyles.html('.discussion-body .post-list a .title { width: ' + titleWidth + 'px !important; }');
-}
diff --git a/lms/static/js/tooltips.js b/lms/static/js/tooltips.js
new file mode 100644
index 0000000000..3cc771d241
--- /dev/null
+++ b/lms/static/js/tooltips.js
@@ -0,0 +1,56 @@
+var $body;
+var $tooltip;
+var tooltipTimer;
+var tooltipCoords;
+$(document).ready(function() {
+ $body = $('body');
+ $tooltip = $('');
+ $body.delegate('[data-tooltip]', {
+ 'mouseover': showTooltip,
+ 'mousemove': moveTooltip,
+ 'mouseout': hideTooltip,
+ 'click': hideTooltip
+ });
+});
+
+function showTooltip(e) {
+ var tooltipText = $(this).attr('data-tooltip');
+ $tooltip.html(tooltipText);
+ $body.append($tooltip);
+ $(this).children().css('pointer-events', 'none');
+
+ tooltipCoords = {
+ x: e.pageX - ($tooltip.outerWidth() / 2),
+ y: e.pageY - ($tooltip.outerHeight() + 15)
+ };
+
+ $tooltip.css({
+ 'left': tooltipCoords.x,
+ 'top': tooltipCoords.y
+ });
+
+ tooltipTimer = setTimeout(function() {
+ $tooltip.show().css('opacity', 1);
+
+ tooltipTimer = setTimeout(function() {
+ hideTooltip();
+ }, 3000);
+ }, 500);
+}
+
+function moveTooltip(e) {
+ tooltipCoords = {
+ x: e.pageX - ($tooltip.outerWidth() / 2),
+ y: e.pageY - ($tooltip.outerHeight() + 15)
+ };
+
+ $tooltip.css({
+ 'left': tooltipCoords.x,
+ 'top': tooltipCoords.y
+ });
+}
+
+function hideTooltip(e) {
+ $tooltip.hide().css('opacity', 0);
+ clearTimeout(tooltipTimer);
+}
diff --git a/lms/templates/discussion/_js_head_dependencies.html b/lms/templates/discussion/_js_head_dependencies.html
index bde873fee1..e2adea7899 100644
--- a/lms/templates/discussion/_js_head_dependencies.html
+++ b/lms/templates/discussion/_js_head_dependencies.html
@@ -12,6 +12,7 @@
+
From bb6f4cdb2eeac002c7809aa7f888b4fa5cf54d1b Mon Sep 17 00:00:00 2001
From: Ibrahim Awwal
Date: Mon, 17 Sep 2012 18:07:52 -0700
Subject: [PATCH 37/82] Move dropdown filter into a more clearly named file,
discussion-filter.js, and move the event bindings into the proper views.
---
.../views/discussion_thread_list_view.coffee | 1 +
.../src/discussion/views/new_post_view.coffee | 1 +
...scussions-temp.js => discussion-filter.js} | 49 ++-----------------
.../discussion/_js_head_dependencies.html | 1 +
lms/templates/discussion/index.html | 2 -
lms/templates/discussion/single_thread.html | 1 -
6 files changed, 6 insertions(+), 49 deletions(-)
rename lms/static/js/{discussions-temp.js => discussion-filter.js} (57%)
diff --git a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
index 6b8abbdc91..d998763f50 100644
--- a/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ b/lms/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -9,6 +9,7 @@ if Backbone?
"click .browse-topic-drop-search-input": "ignoreClick"
"click .post-list .list-item a": "threadSelected"
"click .post-list .more-pages a": "loadMorePages"
+ 'keyup .browse-topic-drop-search-input': DiscussionFilter.filterDrop
initialize: ->
@displayedCollection = new Discussion(@collection.models, pages: @collection.pages)
diff --git a/lms/static/coffee/src/discussion/views/new_post_view.coffee b/lms/static/coffee/src/discussion/views/new_post_view.coffee
index ac5d43b60d..1c49fdbc8e 100644
--- a/lms/static/coffee/src/discussion/views/new_post_view.coffee
+++ b/lms/static/coffee/src/discussion/views/new_post_view.coffee
@@ -21,6 +21,7 @@ if Backbone?
"click .topic_dropdown_button": "toggleTopicDropdown"
"click .topic_menu_wrapper": "setTopic"
"click .topic_menu_search": "ignoreClick"
+ "keyup .form-topic-drop-search-input": DiscussionFilter.filterDrop
# Because we want the behavior that when the body is clicked the menu is
# closed, we need to ignore clicks in the search field and stop propagation.
diff --git a/lms/static/js/discussions-temp.js b/lms/static/js/discussion-filter.js
similarity index 57%
rename from lms/static/js/discussions-temp.js
rename to lms/static/js/discussion-filter.js
index 9ca1a9ba8f..31ef3a8a07 100644
--- a/lms/static/js/discussions-temp.js
+++ b/lms/static/js/discussion-filter.js
@@ -1,49 +1,6 @@
-var $body;
-var $browse;
-var $search;
-var $searchField;
-var $currentBoard;
+var DiscussionFilter = DiscussionFilter || {};
-var $newPost;
-var $sidebar;
-var $sidebarWidthStyles;
-var $postListWrapper;
-var $discussionBody;
-var sidebarWidth;
-var sidebarXOffset;
-var scrollTop;
-
-
-$(document).ready(function() {
- $body = $('body');
- //$browse = $('.browse-search .browse');
- //$search = $('.browse-search .search');
- $searchField = $('.post-search-field');
- //$topicDrop = $('.browse-topic-drop-menu-wrapper');
- $currentBoard = $('.current-board');
-
- $newPost = $('.new-post-article');
- $sidebar = $('.sidebar');
- $discussionBody = $('.discussion-body');
- $postListWrapper = $('.post-list-wrapper');
- // $dropFilter = $('.browse-topic-drop-search-input');
- // $topicFilter = $('.topic-drop-search-input');
- $sidebarWidthStyles = $('');
- $body.append($sidebarWidthStyles);
-
- sidebarWidth = $('.sidebar').width();
- sidebarXOffset = $sidebar.offset().top;
-
- //$browse.bind('click', showTopicDrop);
- //$search.bind('click', showSearch);
- // $topicDrop.bind('click', setTopic);
-// $formTopicDropBtn.bind('click', showFormTopicDrop);
-// $formTopicDropMenu.bind('click', setFormTopic);
-
- $body.delegate('.browse-topic-drop-search-input, .form-topic-drop-search-input', 'keyup', filterDrop);
-});
-
-function filterDrop(e) {
+DiscussionFilter.filterDrop = function (e) {
/*
* multiple queries
*/
@@ -90,7 +47,7 @@ function filterDrop(e) {
* single query
*/
var $drop = $(e.target).parents('.topic_menu_wrapper, .browse-topic-drop-menu-wrapper');
- var query = $(this).val();
+ var query = $(e.target).val();
var $items = $drop.find('a');
if(query.length == 0) {
diff --git a/lms/templates/discussion/_js_head_dependencies.html b/lms/templates/discussion/_js_head_dependencies.html
index e2adea7899..c3a764439c 100644
--- a/lms/templates/discussion/_js_head_dependencies.html
+++ b/lms/templates/discussion/_js_head_dependencies.html
@@ -13,6 +13,7 @@
+
diff --git a/lms/templates/discussion/index.html b/lms/templates/discussion/index.html
index fc96640a35..8f04470506 100644
--- a/lms/templates/discussion/index.html
+++ b/lms/templates/discussion/index.html
@@ -21,8 +21,6 @@
<%include file="_new_post.html" />
-
-
diff --git a/lms/templates/discussion/single_thread.html b/lms/templates/discussion/single_thread.html
index 9ec05b1534..c890c7350f 100644
--- a/lms/templates/discussion/single_thread.html
+++ b/lms/templates/discussion/single_thread.html
@@ -16,7 +16,6 @@
<%block name="js_extra">
<%include file="_js_body_dependencies.html" />
<%static:js group='discussion'/>
-
%block>
From daa38af58c8ea52c42a13f2ffab6bef81f6da2dc Mon Sep 17 00:00:00 2001
From: Ibrahim Awwal
Date: Mon, 17 Sep 2012 18:55:52 -0700
Subject: [PATCH 38/82] Convert DiscussionFilter to coffeescript.
---
.../src/discussion/discussion_filter.coffee | 28 +++++++
lms/static/js/discussion-filter.js | 82 -------------------
2 files changed, 28 insertions(+), 82 deletions(-)
create mode 100644 lms/static/coffee/src/discussion/discussion_filter.coffee
delete mode 100644 lms/static/js/discussion-filter.js
diff --git a/lms/static/coffee/src/discussion/discussion_filter.coffee b/lms/static/coffee/src/discussion/discussion_filter.coffee
new file mode 100644
index 0000000000..6b3ab03689
--- /dev/null
+++ b/lms/static/coffee/src/discussion/discussion_filter.coffee
@@ -0,0 +1,28 @@
+class @DiscussionFilter
+ @filterDrop: (e) ->
+ $drop = $(e.target).parents('.topic_menu_wrapper, .browse-topic-drop-menu-wrapper')
+ query = $(e.target).val()
+ $items = $drop.find('a')
+
+ if(query.length == 0)
+ $items.removeClass('hidden')
+ return;
+
+ $items.addClass('hidden')
+ $items.each (i) ->
+ thisText = $(this).not('.unread').text()
+ $(this).parents('ul').siblings('a').not('.unread').each (i) ->
+ thisText = thisText + ' ' + $(this).text();
+
+ test = true
+ terms = thisText.split(' ')
+
+ if(thisText.toLowerCase().search(query.toLowerCase()) == -1)
+ test = false
+
+ if(test)
+ $(this).removeClass('hidden')
+ # show children
+ $(this).parent().find('a').removeClass('hidden');
+ # show parents
+ $(this).parents('ul').siblings('a').removeClass('hidden');
diff --git a/lms/static/js/discussion-filter.js b/lms/static/js/discussion-filter.js
deleted file mode 100644
index 31ef3a8a07..0000000000
--- a/lms/static/js/discussion-filter.js
+++ /dev/null
@@ -1,82 +0,0 @@
-var DiscussionFilter = DiscussionFilter || {};
-
-DiscussionFilter.filterDrop = function (e) {
- /*
- * multiple queries
- */
-
- // var $drop = $(e.target).parents('.form-topic-drop-menu-wrapper, .browse-topic-drop-menu-wrapper');
- // var queries = $(this).val().split(' ');
- // var $items = $drop.find('a');
-
- // if(queries.length == 0) {
- // $items.show();
- // return;
- // }
-
- // $items.hide();
- // $items.each(function(i) {
- // var thisText = $(this).children().not('.unread').text();
- // $(this).parents('ul').siblings('a').not('.unread').each(function(i) {
- // thisText = thisText + ' ' + $(this).text();
- // });
-
- // var test = true;
- // var terms = thisText.split(' ');
-
- // for(var i = 0; i < queries.length; i++) {
- // if(thisText.toLowerCase().search(queries[i].toLowerCase()) == -1) {
- // test = false;
- // }
- // }
-
- // if(test) {
- // $(this).show();
-
- // // show children
- // $(this).parent().find('a').show();
-
- // // show parents
- // $(this).parents('ul').siblings('a').show();
- // }
- // });
-
-
-
- /*
- * single query
- */
- var $drop = $(e.target).parents('.topic_menu_wrapper, .browse-topic-drop-menu-wrapper');
- var query = $(e.target).val();
- var $items = $drop.find('a');
-
- if(query.length == 0) {
- $items.removeClass('hidden');
- return;
- }
-
- $items.addClass('hidden');
- $items.each(function(i) {
- var thisText = $(this).not('.unread').text();
- $(this).parents('ul').siblings('a').not('.unread').each(function(i) {
- thisText = thisText + ' ' + $(this).text();
- });
-
- var test = true;
- var terms = thisText.split(' ');
-
- if(thisText.toLowerCase().search(query.toLowerCase()) == -1) {
- test = false;
- }
-
- if(test) {
- $(this).removeClass('hidden');
-
- // show children
- $(this).parent().find('a').removeClass('hidden');
-
- // show parents
- $(this).parents('ul').siblings('a').removeClass('hidden');
- }
- });
-}
From aa301a805efb1ef9c4ff3868ad016de9c3d3014a Mon Sep 17 00:00:00 2001
From: Ibrahim Awwal
Date: Mon, 17 Sep 2012 19:19:09 -0700
Subject: [PATCH 39/82] Converted tooltips to coffee too, and removed both
converted files from the included js header.
---
.../coffee/src/discussion/tooltips.coffee | 47 ++++++++++++++++
lms/static/js/tooltips.js | 56 -------------------
.../discussion/_js_head_dependencies.html | 2 -
3 files changed, 47 insertions(+), 58 deletions(-)
create mode 100644 lms/static/coffee/src/discussion/tooltips.coffee
delete mode 100644 lms/static/js/tooltips.js
diff --git a/lms/static/coffee/src/discussion/tooltips.coffee b/lms/static/coffee/src/discussion/tooltips.coffee
new file mode 100644
index 0000000000..d2aa6e4857
--- /dev/null
+++ b/lms/static/coffee/src/discussion/tooltips.coffee
@@ -0,0 +1,47 @@
+$ ->
+ new Tooltips
+
+class @Tooltips
+ constructor: () ->
+ @$body = $('body')
+ @$tooltip = $('')
+ @$body.delegate '[data-tooltip]',
+ 'mouseover': @showTooltip,
+ 'mousemove': @moveTooltip,
+ 'mouseout': @hideTooltip,
+ 'click': @hideTooltip
+
+ showTooltip: (e) =>
+ tooltipText = $(e.target).attr('data-tooltip')
+ @$tooltip.html(tooltipText)
+ @$body.append(@$tooltip)
+ $(e.target).children().css('pointer-events', 'none')
+
+ tooltipCoords =
+ x: e.pageX - (@$tooltip.outerWidth() / 2)
+ y: e.pageY - (@$tooltip.outerHeight() + 15)
+
+ @$tooltip.css
+ 'left': tooltipCoords.x,
+ 'top': tooltipCoords.y
+
+ @tooltipTimer = setTimeout ()=>
+ @$tooltip.show().css('opacity', 1)
+
+ @tooltipTimer = setTimeout ()=>
+ @hideTooltip()
+ , 3000
+ , 500
+
+ moveTooltip: (e) =>
+ tooltipCoords =
+ x: e.pageX - (@$tooltip.outerWidth() / 2)
+ y: e.pageY - (@$tooltip.outerHeight() + 15)
+
+ @$tooltip.css
+ 'left': tooltipCoords.x
+ 'top': tooltipCoords.y
+
+ hideTooltip: (e) =>
+ @$tooltip.hide().css('opacity', 0)
+ clearTimeout(@tooltipTimer)
diff --git a/lms/static/js/tooltips.js b/lms/static/js/tooltips.js
deleted file mode 100644
index 3cc771d241..0000000000
--- a/lms/static/js/tooltips.js
+++ /dev/null
@@ -1,56 +0,0 @@
-var $body;
-var $tooltip;
-var tooltipTimer;
-var tooltipCoords;
-$(document).ready(function() {
- $body = $('body');
- $tooltip = $('');
- $body.delegate('[data-tooltip]', {
- 'mouseover': showTooltip,
- 'mousemove': moveTooltip,
- 'mouseout': hideTooltip,
- 'click': hideTooltip
- });
-});
-
-function showTooltip(e) {
- var tooltipText = $(this).attr('data-tooltip');
- $tooltip.html(tooltipText);
- $body.append($tooltip);
- $(this).children().css('pointer-events', 'none');
-
- tooltipCoords = {
- x: e.pageX - ($tooltip.outerWidth() / 2),
- y: e.pageY - ($tooltip.outerHeight() + 15)
- };
-
- $tooltip.css({
- 'left': tooltipCoords.x,
- 'top': tooltipCoords.y
- });
-
- tooltipTimer = setTimeout(function() {
- $tooltip.show().css('opacity', 1);
-
- tooltipTimer = setTimeout(function() {
- hideTooltip();
- }, 3000);
- }, 500);
-}
-
-function moveTooltip(e) {
- tooltipCoords = {
- x: e.pageX - ($tooltip.outerWidth() / 2),
- y: e.pageY - ($tooltip.outerHeight() + 15)
- };
-
- $tooltip.css({
- 'left': tooltipCoords.x,
- 'top': tooltipCoords.y
- });
-}
-
-function hideTooltip(e) {
- $tooltip.hide().css('opacity', 0);
- clearTimeout(tooltipTimer);
-}
diff --git a/lms/templates/discussion/_js_head_dependencies.html b/lms/templates/discussion/_js_head_dependencies.html
index c3a764439c..bde873fee1 100644
--- a/lms/templates/discussion/_js_head_dependencies.html
+++ b/lms/templates/discussion/_js_head_dependencies.html
@@ -12,8 +12,6 @@
-
-
From 7fb757c86c2e2c93c8c71cc2d1cef6f18de0d930 Mon Sep 17 00:00:00 2001
From: Ibrahim Awwal
Date: Mon, 17 Sep 2012 23:03:36 -0700
Subject: [PATCH 40/82] Rename Tooltips => TooltipManager.
---
.../discussion/{tooltips.coffee => tooltip_manager.coffee} | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
rename lms/static/coffee/src/discussion/{tooltips.coffee => tooltip_manager.coffee} (96%)
diff --git a/lms/static/coffee/src/discussion/tooltips.coffee b/lms/static/coffee/src/discussion/tooltip_manager.coffee
similarity index 96%
rename from lms/static/coffee/src/discussion/tooltips.coffee
rename to lms/static/coffee/src/discussion/tooltip_manager.coffee
index d2aa6e4857..695ad52e3d 100644
--- a/lms/static/coffee/src/discussion/tooltips.coffee
+++ b/lms/static/coffee/src/discussion/tooltip_manager.coffee
@@ -1,7 +1,7 @@
$ ->
- new Tooltips
+ new TooltipManager
-class @Tooltips
+class @TooltipManager
constructor: () ->
@$body = $('body')
@$tooltip = $('')
From d9219c608610f123b4c345b170e3676194c4dea8 Mon Sep 17 00:00:00 2001
From: Victor Shnayder
Date: Sun, 16 Sep 2012 21:55:05 -0400
Subject: [PATCH 41/82] Custom tabs
* specify in tabs list in course policy
- active page tracking now done in tabs.py
- properly handle the fact that there may be multiple textbooks
* Still need:
- wiki pages
- (if that's delayed, special-case syllabus support)
---
common/lib/xmodule/xmodule/course_module.py | 11 +-
common/lib/xmodule/xmodule/modulestore/xml.py | 1 -
doc/xml-format.md | 47 +++-
lms/djangoapps/courseware/tabs.py | 233 ++++++++++++++++++
lms/djangoapps/staticbook/views.py | 6 +-
.../courseware/course_navigation.html | 49 +---
lms/templates/staticbook.html | 2 +-
7 files changed, 301 insertions(+), 48 deletions(-)
create mode 100644 lms/djangoapps/courseware/tabs.py
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 7aa904205d..f0751c5462 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -1,9 +1,9 @@
from fs.errors import ResourceNotFoundError
-import time
import logging
-import requests
from lxml import etree
from path import path # NOTE (THK): Only used for detecting presence of syllabus
+import requests
+import time
from xmodule.util.decorators import lazyproperty
from xmodule.graders import load_grading_policy
@@ -134,6 +134,13 @@ class CourseDescriptor(SequenceDescriptor):
def grade_cutoffs(self):
return self._grading_policy['GRADE_CUTOFFS']
+ @property
+ def tabs(self):
+ """
+ Return the tabs config, as a python object, or None if not specified.
+ """
+ return self.metadata.get('tabs')
+
@property
def show_calculator(self):
return self.metadata.get("show_calculator", None) == "Yes"
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index 82b0abd8ab..874c7d3d7f 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -414,7 +414,6 @@ class XMLModuleStore(ModuleStoreBase):
policy_str = self.read_grading_policy(paths, tracker)
course_descriptor.set_grading_policy(policy_str)
-
log.debug('========> Done with course import from {0}'.format(course_dir))
return course_descriptor
diff --git a/doc/xml-format.md b/doc/xml-format.md
index 29c60fea99..181a814069 100644
--- a/doc/xml-format.md
+++ b/doc/xml-format.md
@@ -219,6 +219,13 @@ Values are dictionaries of the form {"metadata-key" : "metadata-value"}.
* The order in which things appear does not matter, though it may be helpful to organize the file in the same order as things appear in the content.
* NOTE: json is picky about commas. If you have trailing commas before closing braces, it will complain and refuse to parse the file. This can be irritating at first.
+Supported fields at the course level:
+
+* "start" -- specify the start date for the course. Format-by-example: "2012-09-05T12:00".
+* "enrollment_start", "enrollment_end" -- when can students enroll? (if not specified, can enroll anytime). Same format as "start".
+* "tabs" -- have custom tabs in the courseware. See below for details on config.
+* TODO: there are others
+
### Grading policy file contents
TODO: This needs to be improved, but for now here's a sketch of how grading works:
@@ -340,7 +347,45 @@ If you look at some older xml, you may see some tags or metadata attributes that
# Static links
-if your content links (e.g. in an html file) to `"static/blah/ponies.jpg"`, we will look for this in `YOUR_COURSE_DIR/blah/ponies.jpg`. Note that this is not looking in a `static/` subfolder in your course dir. This may (should?) change at some point. Links that include `/course` will be rewritten to the root of your course in the courseware (e.g. `courses/{org}/{course}/{url_name}/` in the current url structure). This is useful for linking to the course wiki, for example.
+If your content links (e.g. in an html file) to `"static/blah/ponies.jpg"`, we will look for this...
+
+* If your course dir has a `static/` subdirectory, we will look in `YOUR_COURSE_DIR/static/blah/ponies.jpg`. This is the prefered organization, as it does not expose anything except what's in `static/` to the world.
+* If your course dir does not have a `static/` subdirectory, we will look in `YOUR_COURSE_DIR/blah/ponies.jpg`. This is the old organization, and requires that the web server allow access to everything in the couse dir. To switch to the new organization, move all your static content into a new `static/` dir (e.g. if you currently have things in `images/`, `css/`, and `special/`, create a dir called `static/`, and move `images/, css/, and special/` there).
+
+Links that include `/course` will be rewritten to the root of your course in the courseware (e.g. `courses/{org}/{course}/{url_name}/` in the current url structure). This is useful for linking to the course wiki, for example.
+
+# Tabs
+
+If you want to customize the courseware tabs displayed for your course, specify a "tabs" list in the course-level policy. e.g.:
+
+"tabs" : [
+{ "type": "courseware"}, # no name--always "Courseware" for consistency between courses
+{"name": "Course Info",
+ "type": course_info"},
+{"name": "My Discussion",
+ "type": external_"link",
+ "link": "http://www.mydiscussion.org/blah"},
+{"name": "Progress",
+"type": "Progress"},
+{"name": "Wonderwiki",
+ "type": "wiki"},
+{"type": "textbooks"} # generates one tab per textbook, taking names from the textbook titles
+]
+
+
+* If you specify any tabs, you must specify all tabs. They will appear in the order given.
+* The first two tabs must have types "courseware" and "course_info", in that order. Otherwise, we'll refuse to load the course.
+* An Instructor tab will be automatically added at the end for course staff users.
+
+## Supported tab types:
+
+* "courseware". No other parameters.
+* "course_info". Parameter "name".
+* "wiki". Parameter "name".
+* "discussion". Parameter "name".
+* "external_link". Parameters "name", "link".
+* "textbooks". No parameters--generates tab names from book titles.
+* "progress". Parameter "name".
# Tips for content developers
diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py
new file mode 100644
index 0000000000..01753625b8
--- /dev/null
+++ b/lms/djangoapps/courseware/tabs.py
@@ -0,0 +1,233 @@
+"""
+Tabs configuration. By the time the tab is being rendered, it's just a name,
+link, and css class (CourseTab tuple). Tabs are specified in course policy.
+Each tab has a type, and possibly some type-specific parameters.
+
+To add a new tab type, add a TabImpl to the VALID_TAB_TYPES dict below--it will
+contain a validation function that checks whether config for the tab type is
+valid, and a generator function that takes the config, user, and course, and
+actually generates the CourseTab.
+"""
+
+from collections import namedtuple
+import logging
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+
+from courseware.access import has_access
+
+log = logging.getLogger(__name__)
+
+class InvalidTabsException(Exception):
+ """
+ A complaint about invalid tabs.
+ """
+ pass
+
+CourseTab = namedtuple('CourseTab', 'name link is_active')
+
+# encapsulate implementation for a tab:
+# - a validation function: takes the config dict and raises
+# InvalidTabsException if required fields are missing or otherwise
+# wrong. (e.g. "is there a 'name' field?). Validators can assume
+# that the type field is valid.
+#
+# - a function that takes a config, a user, and a course, and active_page and
+# return a list of CourseTabs. (e.g. "return a CourseTab with specified
+# name, link to courseware, and is_active=True/False"). The function can
+# assume that it is only called with configs of the appropriate type that
+# have passed the corresponding validator.
+TabImpl = namedtuple('TabImpl', 'validator generator')
+
+
+##### Generators for various tabs.
+
+def _tab(name, view_name, is_active, extra_args=[]):
+ """Return a CourseTab when link is reverse of css class with course_id"""
+ return CourseTab(name, reverse(class_name, args=[course.id]), class_name)
+
+def _courseware(tab, user, course, active_page):
+ link = reverse('courseware', args=[course.id])
+ return [CourseTab('Courseware', link, active_page == "courseware")]
+
+def _course_info(tab, user, course, active_page):
+ link = reverse('info', args=[course.id])
+ return [CourseTab(tab['name'], link, active_page == "info")]
+
+def _progress(tab, user, course, active_page):
+ if user.is_authenticated():
+ link = reverse('progress', args=[course.id])
+ return [CourseTab(tab['name'], link, active_page == "progress")]
+ return []
+
+def _wiki(tab, user, course, active_page):
+ if settings.WIKI_ENABLED:
+ link = reverse('course_wiki', args=[course.id])
+ return [CourseTab(tab['name'], link, active_page == 'wiki')]
+ return []
+
+def _discussion(tab, user, course, active_page):
+ """
+ This tab format only supports the new Berkeley discussion forums.
+ """
+ if settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
+ link = reverse('django_comment_client.forum.views.forum_form_discussion',
+ args=[course.id])
+ return [CourseTab(tab['name'], link, active_page=='discussion')]
+ return []
+
+def _external_link(tab, user, course, active_page):
+ # external links are never active
+ return [CourseTab(tab['name'], tab['link'], False)]
+
+
+def _textbooks(tab, user, course, active_page):
+ """
+ Generates one tab per textbook. Only displays if user is authenticated.
+ """
+ if user.is_authenticated() and settings.MITX_FEATURES.get('ENABLE_TEXTBOOK'):
+ # since there can be more than one textbook, active_page is e.g. "book/0".
+ return [CourseTab(textbook.title, reverse('book', args=[course.id, index]),
+ active_page=="textbook/{0}".format(index))
+ for index, textbook in enumerate(course.textbooks)]
+ return []
+
+#### Validators
+
+
+def key_checker(expected_keys):
+ """
+ Returns a function that checks that specified keys are present in a dict
+ """
+ def check(d):
+ for k in expected_keys:
+ if k not in d:
+ raise InvalidTabsException("Key {0} not present in {1}"
+ .format(k, d))
+ return check
+
+
+need_name = key_checker(['name'])
+
+def null_validator(d):
+ """
+ Don't check anything--use for tabs that don't need any params. (e.g. textbook)
+ """
+ pass
+
+##### The main tab config dict.
+
+# type -> TabImpl
+VALID_TAB_TYPES = {
+ 'courseware': TabImpl(null_validator, _courseware),
+ 'course_info': TabImpl(need_name, _course_info),
+ 'wiki': TabImpl(need_name, _wiki),
+ 'discussion': TabImpl(need_name, _discussion),
+ 'external_link': TabImpl(key_checker(['name', 'link']), _external_link),
+ 'textbooks': TabImpl(null_validator, _textbooks),
+ 'progress': TabImpl(need_name, _progress),
+ }
+
+
+### External interface below this.
+
+def validate_tabs(course):
+ """
+ Check that the tabs set for the specified course is valid. If it
+ isn't, raise InvalidTabsException with the complaint.
+
+ Specific rules checked:
+ - if no tabs specified, that's fine
+ - if tabs specified, first two must have type 'courseware' and 'course_info', in that order.
+ - All the tabs must have a type in VALID_TAB_TYPES.
+
+ """
+ tabs = course.tabs
+ if tabs is None:
+ return
+
+ if len(tabs) < 2:
+ raise InvalidTabsException("Expected at least two tabs. tabs: '{0}'".format(tabs))
+ if tabs[0]['type'] != 'courseware':
+ raise InvalidTabsException(
+ "Expected first tab to have type 'courseware'. tabs: '{0}'".format(tabs))
+ if tabs[1]['type'] != 'course_info':
+ raise InvalidTabsException(
+ "Expected second tab to have type 'course_info'. tabs: '{0}'".format(tabs))
+ for t in tabs:
+ if t['type'] not in VALID_TAB_TYPES:
+ raise InvalidTabsException("Unknown tab type {0}. Known types: {1}"
+ .format(t['type'], VALID_TAB_TYPES))
+ # the type-specific validator checks the rest of the tab config
+ VALID_TAB_TYPES[t['type']].validator(t)
+
+ # Possible other checks: make sure tabs that should only appear once (e.g. courseware)
+ # are actually unique (otherwise, will break active tag code)
+
+
+def get_course_tabs(user, course, active_page):
+ """
+ Return the tabs to show a particular user, as a list of CourseTab items.
+ """
+ if not course.tabs:
+ return get_default_tabs(user, course, active_page)
+
+ # TODO (vshnayder): There needs to be a place to call this right after course
+ # load, but not from inside xmodule, since that doesn't (and probably
+ # shouldn't) know about the details of what tabs are supported, etc.
+ validate_tabs(course)
+
+ tabs = []
+ for tab in course.tabs:
+ # expect handlers to return lists--handles things that are turned off
+ # via feature flags, and things like 'textbook' which might generate
+ # multiple tabs.
+ gen = VALID_TAB_TYPES[tab['type']].generator
+ tabs.extend(gen(tab, user, course, active_page))
+
+ # Instructor tab is special--automatically added if user is staff for the course
+ if has_access(user, course, 'staff'):
+ tabs.append(CourseTab('Instructor',
+ reverse('instructor_dashboard', args=[course.id]),
+ active_page == 'instructor'))
+ return tabs
+
+
+def get_default_tabs(user, course, active_page):
+
+ # When calling the various _tab methods, can omit the 'type':'blah' from the
+ # first arg, since that's only used for dispatch
+ tabs = []
+ tabs.extend(_courseware({''}, user, course, active_page))
+ tabs.extend(_course_info({'name': 'Course Info'}, user, course, active_page))
+
+ if hasattr(course, 'syllabus_present') and course.syllabus_present:
+ link = reverse('syllabus', args=[course.id])
+ tabs.append(CourseTab('Syllabus', link, active_page=='syllabus'))
+
+ tabs.extend(_textbooks({}, user, course, active_page))
+
+ ## If they have a discussion link specified, use that even if we feature
+ ## flag discussions off. Disabling that is mostly a server safety feature
+ ## at this point, and we don't need to worry about external sites.
+ if course.discussion_link:
+ tabs.append(CourseTab('Discussion', course.discussion_link, active_page == 'discussion'))
+ elif settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
+ link = reverse('django_comment_client.forum.views.forum_form_discussion',
+ args=[course.id])
+ tabs.append(CourseTab('Discussion', link, active_page == 'discussion'))
+ elif settings.MITX_FEATURES.get('ENABLE_DISCUSSION'):
+ ## This is Askbot, which we should be retiring soon...
+ tabs.append(CourseTab('Discussion', reverse('questions'), active_page == 'discussion'))
+
+ tabs.extend(_wiki({'name': 'Wiki', 'type': 'wiki'}, user, course, active_page))
+
+ if user.is_authenticated() and not course.hide_progress_tab:
+ tabs.extend(_progress({'name': 'Progress'}, user, course, active_page))
+
+ if has_access(user, course, 'staff'):
+ link = reverse('instructor_dashboard', args=[course.id])
+ tabs.append(CourseTab('Instructor', link, active_page=='instructor'))
+
+ return tabs
diff --git a/lms/djangoapps/staticbook/views.py b/lms/djangoapps/staticbook/views.py
index d68117dd8a..37087af597 100644
--- a/lms/djangoapps/staticbook/views.py
+++ b/lms/djangoapps/staticbook/views.py
@@ -11,11 +11,13 @@ def index(request, course_id, book_index, page=0):
course = get_course_with_access(request.user, course_id, 'load')
staff_access = has_access(request.user, course, 'staff')
- textbook = course.textbooks[int(book_index)]
+ book_index = int(book_index)
+ textbook = course.textbooks[book_index]
table_of_contents = textbook.table_of_contents
return render_to_response('staticbook.html',
- {'page': int(page), 'course': course, 'book_url': textbook.book_url,
+ {'book_index': book_index, 'page': int(page),
+ 'course': course, 'book_url': textbook.book_url,
'table_of_contents': table_of_contents,
'staff_access': staff_access})
diff --git a/lms/templates/courseware/course_navigation.html b/lms/templates/courseware/course_navigation.html
index ffa8b0cadd..5ae69908fb 100644
--- a/lms/templates/courseware/course_navigation.html
+++ b/lms/templates/courseware/course_navigation.html
@@ -6,54 +6,21 @@ if active_page == None and active_page_context is not UNDEFINED:
# If active_page is not passed in as an argument, it may be in the context as active_page_context
active_page = active_page_context
-def url_class(url):
- if url == active_page:
+def url_class(is_active):
+ if is_active:
return "active"
return ""
%>
-<%! from django.core.urlresolvers import reverse %>
-<%! from courseware.access import has_access %>
+<%! from courseware.tabs import get_course_tabs %>