From 99d6296b994d049a6ad8d3f13c0b3f2edda1a2fe Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 29 Aug 2012 15:39:03 -0400 Subject: [PATCH 01/48] Set the course static file dir to /static directories in course repos if the folder exists, and fall back to including the whole course folder otherwise --- lms/envs/common.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index ce08bf9666..61a3abab6d 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -260,12 +260,22 @@ STATICFILES_DIRS = [ PROJECT_ROOT / "askbot" / "skins", ] 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) + 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 From 08b233f6037940f50b26f395931549c74b2aaa78 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Tue, 28 Aug 2012 22:38:56 -0700 Subject: [PATCH 02/48] Ensure that js files are returned in alphabetical order by django-pipeline. On most systems this seems to be the default behavior, but glob2.glob's order isn't guaranteed and on my machine the order seems to be random, which causes javascript which depends on include order to fail. --- lms/envs/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index ce08bf9666..37d65e78f9 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -429,7 +429,7 @@ main_vendor_js = [ 'js/vendor/jquery.qtip.min.js', ] -discussion_js = glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/*.coffee') +discussion_js = sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/*.coffee')) # Load javascript from all of the available xmodules, and # prep it for use in pipeline js @@ -497,10 +497,10 @@ PIPELINE_JS = { 'source_filenames': [ pth.replace(COMMON_ROOT / 'static/', '') for pth - in glob2.glob(COMMON_ROOT / 'static/coffee/src/**/*.coffee') + in sorted(glob2.glob(COMMON_ROOT / 'static/coffee/src/**/*.coffee')) ] + [ pth.replace(PROJECT_ROOT / 'static/', '') - for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')\ + for pth in sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee'))\ if pth not in courseware_only_js and pth not in discussion_js ] + [ 'js/form.ext.js', From e58af44620e495e4006938d865a8a994ff1fc0d1 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Wed, 29 Aug 2012 19:41:46 -0700 Subject: [PATCH 03/48] Annotate comment content wrappers with classes for the roles of the author. --- lms/djangoapps/django_comment_client/utils.py | 5 +++++ lms/templates/discussion/mustache/_content.mustache | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 516344d79b..dc4a4e21e1 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -8,7 +8,9 @@ from django.utils import simplejson from django.db import connection from django.conf import settings from django.core.urlresolvers import reverse +from django.contrib.auth.models import User from django_comment_client.permissions import check_permissions_by_view +from django_comment_client.models import Role from mitxmako import middleware import logging @@ -212,11 +214,14 @@ def permalink(content): args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id'] def extend_content(content): + user = User.objects.get(pk=content['user_id']) + roles = dict([('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id'])]) content_info = { 'displayed_title': content.get('highlighted_title') or content.get('title', ''), 'displayed_body': content.get('highlighted_body') or content.get('body', ''), 'raw_tags': ','.join(content.get('tags', [])), 'permalink': permalink(content), + 'roles': roles, } return merge_dict(content, content_info) diff --git a/lms/templates/discussion/mustache/_content.mustache b/lms/templates/discussion/mustache/_content.mustache index b4f3176931..03a9211b35 100644 --- a/lms/templates/discussion/mustache/_content.mustache +++ b/lms/templates/discussion/mustache/_content.mustache @@ -1,5 +1,5 @@
-
+
{{content.votes.point}}
From e1aa30b36f84abf55bf1bed1b9e9f3937dd8d575 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Wed, 29 Aug 2012 21:52:22 -0700 Subject: [PATCH 04/48] Style comments from moderators in light blue, and admins in light green. Also puts text after the author's name (there should be something for colorblind people, not sure what form it should take though). --- lms/static/sass/_discussion.scss | 15 +++++++++++++++ .../discussion/mustache/_content.mustache | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 6c1988684e..869148a795 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -385,6 +385,14 @@ $tag-text-color: #5b614f; color: #dea03e; } } + + .author-moderator:after{ + content: " (moderator)" + } + + .author-administrator:after{ + content: " (administrator)" + } } .discussion-content { @@ -410,6 +418,13 @@ $tag-text-color: #5b614f; } } + // Role based styles + .role-moderator{ + background-color: #eafcfc; + } + .role-administrator{ + background-color: #eafcea; + } //COMMENT STYLES .comments { overflow: hidden; diff --git a/lms/templates/discussion/mustache/_content.mustache b/lms/templates/discussion/mustache/_content.mustache index 03a9211b35..f6b375dc88 100644 --- a/lms/templates/discussion/mustache/_content.mustache +++ b/lms/templates/discussion/mustache/_content.mustache @@ -1,5 +1,5 @@ -
-
+
+
{{content.votes.point}}
@@ -34,7 +34,7 @@ anonymous {{/content.anonymous}} {{^content.anonymous}} - {{content.username}} + {{content.username}} {{/content.anonymous}}
From b80ceecf87e2c9663ac9a88b1e4f420683d2c929 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Thu, 30 Aug 2012 00:19:42 -0700 Subject: [PATCH 05/48] Note when posts have been updated, and put the creation date in the title text. --- lms/djangoapps/django_comment_client/utils.py | 1 + lms/static/coffee/src/discussion/content.coffee | 3 +++ lms/templates/discussion/mustache/_content.mustache | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index dc4a4e21e1..89d3cd99ba 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -222,6 +222,7 @@ def extend_content(content): 'raw_tags': ','.join(content.get('tags', [])), 'permalink': permalink(content), 'roles': roles, + 'updated': content['created_at']!=content['updated_at'], } return merge_dict(content, content_info) diff --git a/lms/static/coffee/src/discussion/content.coffee b/lms/static/coffee/src/discussion/content.coffee index 73c3688c2a..9f95c201f4 100644 --- a/lms/static/coffee/src/discussion/content.coffee +++ b/lms/static/coffee/src/discussion/content.coffee @@ -374,6 +374,9 @@ if Backbone? MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")] initTimeago: -> + @$("span.timeago").each (index, element) -> + elem = $(element) + elem.html("posted on #{$.timeago.parse(elem.html()).toLocaleString()}") @$("span.timeago").timeago() renderPartial: -> diff --git a/lms/templates/discussion/mustache/_content.mustache b/lms/templates/discussion/mustache/_content.mustache index f6b375dc88..264115919b 100644 --- a/lms/templates/discussion/mustache/_content.mustache +++ b/lms/templates/discussion/mustache/_content.mustache @@ -29,7 +29,10 @@ {{/thread}}
- sometime by + {{#content.updated}} + updated + {{/content.updated}} + {{content.created_at}} by {{#content.anonymous}} anonymous {{/content.anonymous}} From b276d8b4b11c422ec9fade45e838e98b3772fb9f Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 7 Sep 2012 09:47:13 -0400 Subject: [PATCH 06/48] Changes as per cpennington's code review --- lms/djangoapps/django_comment_client/utils.py | 2 +- lms/static/sass/_discussion.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 89d3cd99ba..6da7130c52 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -215,7 +215,7 @@ def permalink(content): def extend_content(content): user = User.objects.get(pk=content['user_id']) - roles = dict([('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id'])]) + roles = dict(('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id'])) content_info = { 'displayed_title': content.get('highlighted_title') or content.get('title', ''), 'displayed_body': content.get('highlighted_body') or content.get('body', ''), diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 869148a795..f19dbe36ff 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -391,7 +391,7 @@ $tag-text-color: #5b614f; } .author-administrator:after{ - content: " (administrator)" + content: " (instructor)" } } From 012edbfd40079586c8750c53672b496fbacf6de7 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 11:14:26 -0400 Subject: [PATCH 07/48] Fix main navigation and footer links also make text not italic --- lms/static/sass/course/base/_base.scss | 1 + lms/static/sass/course/layout/_courseware_header.scss | 4 ++-- lms/static/sass/shared/_footer.scss | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 1edb9aa7ba..9d6cf81c9f 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -2,6 +2,7 @@ body { min-width: 980px; min-height: 100%; background: url(../images/bg-texture.png) #d6d6d6; + font-style: normal; } body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label { diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index 86b5fe58c5..f278339523 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -40,9 +40,9 @@ nav.course-material { } &.active { - // background: rgba(0, 0, 0, .2); + background-color: #333; + background-color: rgba(0, 0, 0, 0); @include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25)); - background-color: transparent; @include box-shadow(0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset); color: #fff; text-shadow: 0 1px 0 rgba(0, 0, 0, .4); diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index d182ed4316..a418b887ad 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -110,6 +110,7 @@ footer { padding: 0; a { + @include inline-block; opacity: 0.3; @include transition(all, 0.1s, linear); From aec8cc42dcda83457b81986a0ce01ba3b7bc6655 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 12:59:30 -0400 Subject: [PATCH 08/48] Add ie fix for wiki --- lms/static/sass/course/base/_base.scss | 5 ----- lms/static/sass/course/layout/_courseware_header.scss | 3 +-- lms/static/sass/course/wiki/_wiki.scss | 2 +- lms/static/sass/ie.scss | 11 +++++++++++ lms/templates/main.html | 6 +++--- lms/templates/main_django.html | 8 +++++++- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 9d6cf81c9f..902c8025fd 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -40,11 +40,6 @@ a { } } - - - - - form { label { display: block; diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index f278339523..103139b330 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -40,9 +40,8 @@ nav.course-material { } &.active { - background-color: #333; - background-color: rgba(0, 0, 0, 0); @include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25)); + background-color: transparent; @include box-shadow(0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset); color: #fff; text-shadow: 0 1px 0 rgba(0, 0, 0, .4); diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index 1bc38abd9a..84260e2a86 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -397,7 +397,7 @@ section.wiki { #hint_id_content { position: absolute; - top: 10px; + top: 4px; right: 0%; font-size: 12px; text-align:right; diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index 35997d95a7..ff5608deaf 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -133,3 +133,14 @@ header.global { .modal .inner-wrapper form label { display: block; } + +nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { + background-color: #333; + background-color: rgba(0, 0, 0, .4); +} + +header.global ol.user > li.primary a.dropdown { + padding-top: 6px; + padding-bottom: 6px; +} + diff --git a/lms/templates/main.html b/lms/templates/main.html index 51b8d6030e..ec0b907fdf 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -8,9 +8,6 @@ <%static:css group='application'/> - <%static:js group='main_vendor'/> <%block name="headextra"/> @@ -18,6 +15,9 @@ + diff --git a/lms/templates/main_django.html b/lms/templates/main_django.html index d300e666a9..fa59f0128b 100644 --- a/lms/templates/main_django.html +++ b/lms/templates/main_django.html @@ -11,7 +11,13 @@ {% block headextra %}{% endblock %} {% render_block "css" %} + + + + @@ -40,4 +46,4 @@ Inheriting from this file allows us to include apps that use the django templating system without rewriting all of their views in mako. -{% endcomment %} \ No newline at end of file +{% endcomment %} From 4d4e5824cdd0c11ebac601bf15a5fa99679232ee Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 13:29:49 -0400 Subject: [PATCH 09/48] Change form style for login and signup modals so it is consistant in every browser and we have no usability problems --- lms/static/sass/ie.scss | 4 ---- lms/static/sass/shared/_modal.scss | 11 +++++++++-- lms/templates/login_modal.html | 4 ++-- lms/templates/signup_modal.html | 14 +++++++------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index ff5608deaf..a22d3935fd 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -130,10 +130,6 @@ header.global { background: #000; } -.modal .inner-wrapper form label { - display: block; -} - nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { background-color: #333; background-color: rgba(0, 0, 0, .4); diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index 409c6088ec..b7ea762ce8 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -147,10 +147,9 @@ } label { - display: none; + color: #999; &.field-error { - display: block; color: #8F0E0E; + input { @@ -164,6 +163,14 @@ margin-right: 5px; } + ::-webkit-input-placeholder { + color: #ddd; + } + + :-moz-placeholder { + color: #ddd; + } + textarea { background: rgb(255,255,255); display: block; diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index 8652f457e6..d7d327178c 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -10,9 +10,9 @@
From bce61cd6e50c2fe119e1270b6e8841d78a6adaab Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 14:18:34 -0400 Subject: [PATCH 10/48] Revert "Merge pull request #642 from MITx/bug/kfiedler/ie" This reverts commit 2b5e8a49441bb75f356420fbd814401934990c01, reversing changes made to db0549be78679562dbab5a04401e450a1d24b619. --- lms/static/sass/course/base/_base.scss | 6 +++++- .../sass/course/layout/_courseware_header.scss | 1 + lms/static/sass/course/wiki/_wiki.scss | 2 +- lms/static/sass/ie.scss | 11 ++--------- lms/static/sass/shared/_footer.scss | 1 - lms/static/sass/shared/_modal.scss | 11 ++--------- lms/templates/login_modal.html | 4 ++-- lms/templates/main.html | 6 +++--- lms/templates/main_django.html | 8 +------- lms/templates/signup_modal.html | 14 +++++++------- 10 files changed, 24 insertions(+), 40 deletions(-) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 902c8025fd..1edb9aa7ba 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -2,7 +2,6 @@ body { min-width: 980px; min-height: 100%; background: url(../images/bg-texture.png) #d6d6d6; - font-style: normal; } body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label { @@ -40,6 +39,11 @@ a { } } + + + + + form { label { display: block; diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index 103139b330..86b5fe58c5 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -40,6 +40,7 @@ nav.course-material { } &.active { + // background: rgba(0, 0, 0, .2); @include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25)); background-color: transparent; @include box-shadow(0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset); diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index 84260e2a86..1bc38abd9a 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -397,7 +397,7 @@ section.wiki { #hint_id_content { position: absolute; - top: 4px; + top: 10px; right: 0%; font-size: 12px; text-align:right; diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index a22d3935fd..35997d95a7 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -130,13 +130,6 @@ header.global { background: #000; } -nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { - background-color: #333; - background-color: rgba(0, 0, 0, .4); +.modal .inner-wrapper form label { + display: block; } - -header.global ol.user > li.primary a.dropdown { - padding-top: 6px; - padding-bottom: 6px; -} - diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index a418b887ad..d182ed4316 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -110,7 +110,6 @@ footer { padding: 0; a { - @include inline-block; opacity: 0.3; @include transition(all, 0.1s, linear); diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index b7ea762ce8..409c6088ec 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -147,9 +147,10 @@ } label { - color: #999; + display: none; &.field-error { + display: block; color: #8F0E0E; + input { @@ -163,14 +164,6 @@ margin-right: 5px; } - ::-webkit-input-placeholder { - color: #ddd; - } - - :-moz-placeholder { - color: #ddd; - } - textarea { background: rgb(255,255,255); display: block; diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index d7d327178c..8652f457e6 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -10,9 +10,9 @@ - + - +
From 8500a1b300bda258026fa09b6a3e37b760580d9d Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 7 Sep 2012 14:38:53 -0400 Subject: [PATCH 11/48] Make tests not use syslog when they're running --- lms/envs/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/envs/test.py b/lms/envs/test.py index b55cf2af5f..34108256e9 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -46,6 +46,7 @@ DATA_DIR = COURSES_ROOT LOGGING = get_logger_config(TEST_ROOT / "log", logging_env="dev", tracking_filename="tracking.log", + dev_env=True, debug=True) COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data" From bbd57ef79b6fbbd8b923c9367c84fe1d18b9ad52 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 7 Sep 2012 15:10:26 -0400 Subject: [PATCH 12/48] don't show empty list if there is no error data --- .../courseware/instructor_dashboard.html | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index 2d9ab853eb..8568490e5e 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -64,17 +64,17 @@ table.stat_table td { %if instructor_access:

- +

- - + +


%endif - + %if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access:

- - + + %endif @@ -101,7 +101,7 @@ table.stat_table td {

%if msg: -

${msg}

+

${msg}

%endif % if course_errors is not UNDEFINED: @@ -110,13 +110,17 @@ table.stat_table td {
    % for (summary, err) in course_errors:
  • ${summary | h} + % if err:
    • ${err | h}
    + % else: +

     

    + % endif
  • % endfor
% endif - +
From 9ad9359d48d77cc929984d11ad3df0bfd567700f Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Fri, 7 Sep 2012 15:57:43 -0400 Subject: [PATCH 13/48] quick wording change --- lms/templates/courseware/welcome-back.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/courseware/welcome-back.html b/lms/templates/courseware/welcome-back.html index a817d303a9..5d4e0fe1e3 100644 --- a/lms/templates/courseware/welcome-back.html +++ b/lms/templates/courseware/welcome-back.html @@ -1,3 +1,3 @@

${chapter_module.display_name}

-

You were last in ${prev_section.display_name}. If you're done with that, choose another section on the left.

\ No newline at end of file +

You were most recently in ${prev_section.display_name}. If you're done with that, choose another section on the left.

From b85fefe61a146b6f39f7d99f2b620bd8bf5cdd74 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 7 Sep 2012 17:02:00 -0400 Subject: [PATCH 14/48] Fixing tests that were failing to due static content directory change --- .../html/sound_labs/mosfet_amplifier.html | 124 -- .../data/full/html/sound_labs/rc_filters.html | 111 -- .../data/full/html/sound_labs/series_rlc.html | 111 -- .../full/{ => static}/circuits/120V60Hz.gif | Bin .../full/{ => static}/circuits/Lab1_1.png | Bin .../full/{ => static}/circuits/duality.gif | Bin .../{ => static}/circuits/heaters-bad.gif | Bin .../circuits/heaters-parallel.gif | Bin .../handouts/schematic_tutorial.pdf | Bin .../test/data/full/{ => static}/js/cktsim.js | 0 .../data/full/{ => static}/js/schematic.js | 0 .../data/full/static/js/sound_labs/circuit.js | 1247 ----------------- .../static/js/sound_labs/mosfet_amplifier.js | 658 --------- .../data/full/static/js/sound_labs/plotter.js | 1038 -------------- .../full/static/js/sound_labs/rc_filters.js | 938 ------------- .../full/static/js/sound_labs/series_rlc.js | 1150 --------------- .../data/full/static/js/sound_labs/sound.js | 407 ------ 17 files changed, 5784 deletions(-) delete mode 100644 common/test/data/full/html/sound_labs/mosfet_amplifier.html delete mode 100644 common/test/data/full/html/sound_labs/rc_filters.html delete mode 100644 common/test/data/full/html/sound_labs/series_rlc.html rename common/test/data/full/{ => static}/circuits/120V60Hz.gif (100%) rename common/test/data/full/{ => static}/circuits/Lab1_1.png (100%) rename common/test/data/full/{ => static}/circuits/duality.gif (100%) rename common/test/data/full/{ => static}/circuits/heaters-bad.gif (100%) rename common/test/data/full/{ => static}/circuits/heaters-parallel.gif (100%) rename common/test/data/full/{ => static}/handouts/schematic_tutorial.pdf (100%) rename common/test/data/full/{ => static}/js/cktsim.js (100%) rename common/test/data/full/{ => static}/js/schematic.js (100%) delete mode 100644 common/test/data/full/static/js/sound_labs/circuit.js delete mode 100644 common/test/data/full/static/js/sound_labs/mosfet_amplifier.js delete mode 100644 common/test/data/full/static/js/sound_labs/plotter.js delete mode 100644 common/test/data/full/static/js/sound_labs/rc_filters.js delete mode 100644 common/test/data/full/static/js/sound_labs/series_rlc.js delete mode 100644 common/test/data/full/static/js/sound_labs/sound.js diff --git a/common/test/data/full/html/sound_labs/mosfet_amplifier.html b/common/test/data/full/html/sound_labs/mosfet_amplifier.html deleted file mode 100644 index c7f2f1ad81..0000000000 --- a/common/test/data/full/html/sound_labs/mosfet_amplifier.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - -

LAB 5B: MOSFET AMPLIFIER EXPERIMENT

-
- -

Note: This part of the lab is just to develop your intuition about -amplifiers and biasing, and to have fun with music! There are no responses -that need to be checked.

-

The graph plots the selected voltages from the amplifier circuit below. You -can also listen to various signals by selecting from the radio buttons to -the right of the graph. This way you can both see and hear various signals. -You can use the sliders to the right of the amplifier circuit to control -various parameters of the MOSFET and the amplifier. The parameter \(V_{MAX}\) -sets the maximum range on the plots. You can also select an input voltage -type (e.g., sine wave, square wave, various types of music) using the drop -down menu to the right of the graph. When describing AC signals, the -voltages on the sliders refer to peak-to-peak values.

-

1. To begin your first experiment, go ahead and use the pull down menu to -select a sine wave input. Then, adjust the sliders to an approximate -baseline setting shown below.

-

Baseline setting of sliders: -
-\(V_{S}=1.6V\), \(v_{IN}=3V\), \(Frequency=1000Hz\), \(V_{BIAS}=2.5V\), \(R=10K\Omega\), \(k=1mA/V^{2}\), \(V_{T}=1V\), \(V_{MAX}=2V\).

-

You will observe in the plot that the baseline setting of the sliders for -the various amplifiers parameters produces a distorted sine wave signal for -\(v_{OUT}\). Next, go ahead and select one of the music signals as the input and -listen to each of \(v_{IN}\) and \(v_{OUT}\), and confirm for yourself that the -output sounds distorted for the chosen slider settings. You will notice -that the graph now plots the music signal waveforms. Think about all the -reasons why the amplifier is producing a distorted output.

-

2. For the second experiment, we will study the amplifier's small signal -behavior. Select a sine wave as the input signal. To study the small -signal behavior, reduce the value of \(v_{IN}\) to 0.1V (peak-to-peak) by -using the \(v_{IN}\) slider. Keeping the rest of the parameters at their -baseline settings, derive an appropriate value of \(V_{BIAS}\) that will ensure -saturation region operation for the MOSFET for the 0.1V peak-to-peak swing -for \(v_{IN}\). Make sure to think about both positive and negative excursions -of the signals.

-

Next, use the \(V_{BIAS}\) slider to choose your computed value for \(V_{BIAS}\) and -see if the observed plot of \(v_{OUT}\) is more or less distortion free. If -your calculation was right, then the output will indeed be distortion free.

-

Next, select one of the music signals as the input and listen to each of -\(v_{IN}\) and \(v_{OUT}\), and confirm for yourself that the output sounds much -better than in Step 1. Also, based on sound volume, confirm that \(v_{OUT}\) is -an amplified version of \(v_{IN}\).

-

3. Now go ahead and experiment with various other settings while listening -to the music signal at \(v_{OUT}\). Observe the plots and listen to \(v_{OUT}\) as -you change, for example, the bias voltage \(V_{BIAS}\). You will notice that -the amplifier distorts the input signal when \(V_{BIAS}\) becomes too small, or -when it becomes too large. You can also experiment with various values of -\(v_{IN}\), \(R_{L}\), etc., and see how they affect the amplification and distortion.

- -
- -
-
- -
-
- - - -
- -
-
-

Graph:

-
    -
  • -
  • -
  • -
-
- -
-

Listen to:

-
    -
  • -
  • -
  • -
-
-
- -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- -
diff --git a/common/test/data/full/html/sound_labs/rc_filters.html b/common/test/data/full/html/sound_labs/rc_filters.html deleted file mode 100644 index c52f8022a4..0000000000 --- a/common/test/data/full/html/sound_labs/rc_filters.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - -

LAB 10B: RC FILTERS WITH FREQUENCY RESPONSE EXPERIMENT

-
- -

Note: Use this part of the lab to build your intuition about filters and frequency response, and to have fun with music! There are no responses that need to be checked.

-

Recall from the audio lab in Week 5 that the graph plots the selected voltages from the circuit shown below. This week the circuit is an RC filter. You can also listen to various signals by selecting from the radio buttons to the right of the graph. This way you can both see and hear various signals. You can use the sliders to the right of the circuit to control various circuit and input signal parameters. (Note that you can get finer control of some of the slider values by clicking on the slider and using the arrow keys). Recall that the parameter \(V_{MAX}\) sets the maximum range on the graph. You can also select an input voltage type (e.g., sine wave, square wave, various types of music) using the drop down menu to the right of the graph. When describing AC signals, the voltages on the sliders refer to peak-to-peak values.

-

1. To begin your first experiment, use the pull down menu to select a sine wave input. Then, adjust the sliders to these approximate baseline settings: -
-\(v_{IN} = 3V\), \(Frequency = 1000 Hz\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\). -
-Observe the waveforms for \(v_{IN}\) and \(v_C\) in the graph. You can also listen to \(v_{IN}\) and \(v_C\). You will observe that the amplitude of \(v_C\) is slightly smaller than the amplitude of \(v_{IN}\). -
-Compute the break frequency of the filter circuit for the given circuit parameters. (Note that the break frequency is also called the cutoff frequency or the corner frequency). -
-Change the frequency of the sinusoid so that it is approximately 3 times the break frequency. -
-Observe the waveforms for \(v_{IN}\) and \(v_C\) in the graph. Also listen to \(v_{IN}\) and \(v_C\). Think about why the sinusoid at \(v_C\) is significantly more attenuated than the original 1KHz sinusoid. -
-Keeping the input signal unchanged, observe the waveforms for \(v_{IN}\) and \(v_R\) in the graph. Also listen to \(v_{IN}\) and \(v_R\). Think about why the sinusoid at \(v_R\) is significantly louder than the sinusoid at \(v_C\).

-

2. Next, use the pull down menu to select a music signal of your choice. Adjust the sliders to the approximate baseline settings: -
-\(v_{IN} = 3V\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\). -
-Listen to the signals at \(v_{IN}\) and \(v_C\). Notice any difference between the signals? -
-Next, increase the capacitance value and observe the difference in the sound of \(v_{IN}\) and \(v_C\) as the capacitance increases. You should notice that the higher frequency components of \(v_C\) are attenuated as the capacitance is increased. -Convince yourself that when the signal is taken at \(v_C\), the circuit behaves like a low-pass filter.

-

3. Re-adjust the sliders to the approximate baseline settings: -
-\(v_{IN} = 3V\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\). -
-Try to create a high-pass filter from the same circuit by taking the signal output across a different element and possibly changing some of the element values. -

- -
- -
-
-
-
- - -
- -
-
-

Graph:

-
    -
  • -
  • -
  • -
-
- -
-

Listen to:

-
    -
  • -
  • -
  • -
-
-
-
-
-
fC =
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- -
diff --git a/common/test/data/full/html/sound_labs/series_rlc.html b/common/test/data/full/html/sound_labs/series_rlc.html deleted file mode 100644 index 4856d5c5ed..0000000000 --- a/common/test/data/full/html/sound_labs/series_rlc.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - -

SERIES RLC CIRCUIT WITH FREQUENCY RESPONSE EXPERIMENT

-
- -

\(I(s) = \frac{1}{R + Ls + 1/Cs}V_{in}(s) = \frac{s/L}{s^2 + sR/L + 1/LC}V_{in}(s)\)

-

\(I(s) = \frac{s/L}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s)\)

-

\(\omega_0 = \frac{1}{\sqrt{LC}} , \alpha = \frac{R}{2L}\)

-

Band-Pass Filter:

-

\(V_r(s) = RI(s) = \frac{sR/L}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{2\alpha s}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{2\alpha s}{(s-s_1)(s-s_2)}V_{in}(s)\)

-

Gain magnitude: \(G_R = \frac{2\alpha w}{|j\omega - s_1||j\omega - s_2|}\)

-

Phase: \(\Phi_R = \pi/2-\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)

-

Low-Pass Filter:

-

\(V_c(s) = I(s)/sC = \frac{1/LC}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{\omega_0^2}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{\omega_0^2}{(s-s_1)(s-s_2)}V_{in}(s)\)

-

Gain magnitude: \(G_C = \frac{\omega_0^2}{|j\omega - s_1||j\omega - s_2|}\)

-

Phase: \(\Phi_C = -\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)

-

High-Pass Filter:

-

\(V_l(s) = sLI(s) = \frac{s^2}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{s^2}{(s-s_1)(s-s_2)}V_{in}(s)\)

-

Gain magnitude: \(G_L = \frac{\omega^2}{|j\omega - s_1||j\omega - s_2|}\)

-

Phase: \(\Phi_L = -\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)

-
-

Under-Damped: \(\alpha < \omega_0\)

-

Complex roots: \(s_{1,2} = -\alpha \pm j\sqrt{\omega_0^2 - \alpha^2}\)

-

Critically-Damped: \(\alpha = \omega_0\)

-

Double real root: \(s_{1,2} = -\alpha\)

-

Over-Damped: \(\alpha > \omega_0\)

-

Real roots: \(s_{1,2} = -\alpha \pm\sqrt{\alpha^2 - \omega_0^2}\)

- -
- -
-
- -
-
- - -
- -
-
-

Graph:

-
    -
  • -
  • -
  • -
  • -
-
- -
-

Listen to:

-
    -
  • -
  • -
  • -
  • -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - -
- -
diff --git a/common/test/data/full/circuits/120V60Hz.gif b/common/test/data/full/static/circuits/120V60Hz.gif similarity index 100% rename from common/test/data/full/circuits/120V60Hz.gif rename to common/test/data/full/static/circuits/120V60Hz.gif diff --git a/common/test/data/full/circuits/Lab1_1.png b/common/test/data/full/static/circuits/Lab1_1.png similarity index 100% rename from common/test/data/full/circuits/Lab1_1.png rename to common/test/data/full/static/circuits/Lab1_1.png diff --git a/common/test/data/full/circuits/duality.gif b/common/test/data/full/static/circuits/duality.gif similarity index 100% rename from common/test/data/full/circuits/duality.gif rename to common/test/data/full/static/circuits/duality.gif diff --git a/common/test/data/full/circuits/heaters-bad.gif b/common/test/data/full/static/circuits/heaters-bad.gif similarity index 100% rename from common/test/data/full/circuits/heaters-bad.gif rename to common/test/data/full/static/circuits/heaters-bad.gif diff --git a/common/test/data/full/circuits/heaters-parallel.gif b/common/test/data/full/static/circuits/heaters-parallel.gif similarity index 100% rename from common/test/data/full/circuits/heaters-parallel.gif rename to common/test/data/full/static/circuits/heaters-parallel.gif diff --git a/common/test/data/full/handouts/schematic_tutorial.pdf b/common/test/data/full/static/handouts/schematic_tutorial.pdf similarity index 100% rename from common/test/data/full/handouts/schematic_tutorial.pdf rename to common/test/data/full/static/handouts/schematic_tutorial.pdf diff --git a/common/test/data/full/js/cktsim.js b/common/test/data/full/static/js/cktsim.js similarity index 100% rename from common/test/data/full/js/cktsim.js rename to common/test/data/full/static/js/cktsim.js diff --git a/common/test/data/full/js/schematic.js b/common/test/data/full/static/js/schematic.js similarity index 100% rename from common/test/data/full/js/schematic.js rename to common/test/data/full/static/js/schematic.js diff --git a/common/test/data/full/static/js/sound_labs/circuit.js b/common/test/data/full/static/js/sound_labs/circuit.js deleted file mode 100644 index dfd756a4a2..0000000000 --- a/common/test/data/full/static/js/sound_labs/circuit.js +++ /dev/null @@ -1,1247 +0,0 @@ -var Circuit = (function() { - - var Color = - { - background : "rgb(0, 51, 102)", //0.0, 0.2, 0.4 - black : "rgb(0, 0, 0)", //0.0 - lodarkgray : "rgb(26, 26, 26)", //0.1 = 25.5 - darkgray : "rgb(51, 51, 51)", //0.2 - lomidgray : "rgb(102, 102, 102)", //0.4 - midgray : "rgb(128, 128, 128)", //0.5 = 127.5 - himidgray : "rgb(153, 153, 153)", //0.6 - litegray : "rgb(204, 204, 204)", //0.8 - white : "rgb(255, 255, 255)", //1.0 - - red : "rgb(255, 0, 0)", - green : "rgb(0, 255, 0)", - blue : "rgb(0, 0, 255)", - yellow : "rgb(255, 255, 0)", - cyan : "rgb(0, 255, 255)", - magenta : "rgb(255, 0, 255)" - }; - - var Utils = - { - TWO_PI: 2.0*Math.PI, - PI_DIV_2: Math.PI/2.0 - }; - - function distance(x1, y1, x2, y2) - { - var dx = x2 - x1; - var dy = y2 - y1; - - return Math.sqrt(dx * dx + dy * dy); - } - - function transform(x, y, xt, yt, rot) - { - //First translate - x -= xt; - y -= yt; - //Then rotate - return {x: x * Math.cos(rot) - y * Math.sin(rot), y: x * Math.sin(rot) + y * Math.cos(rot)}; - } - - function closestGridPoint(gridStep, x) - { - return gridStep * Math.round(x / gridStep); - } - - function getMousePosition(diagram, event) - { - var mouseX = event.pageX - (parseInt(diagram.element.offset().left) + parseInt(diagram.element.css('paddingLeft')) + parseInt(diagram.element.css('borderLeftWidth'))); - var mouseY = event.pageY - (parseInt(diagram.element.offset().top) + parseInt(diagram.element.css('paddingTop')) + parseInt(diagram.element.css('borderTopWidth'))); - return {x : mouseX, y : mouseY}; - } - - function diagramMouseDown(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - var mpos = getMousePosition(diagram, event); - - for(var i = 0, len = diagram.components.length; i < len; i++) - { - if(diagram.components[i].isInside(mpos.x, mpos.y)) - { - diagram.components[i].selected = true; - diagram.startx = closestGridPoint(diagram.gridStep, mpos.x); - diagram.starty = closestGridPoint(diagram.gridStep, mpos.y); - } - } - - return false; - } - - function diagramMouseMove(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - var mpos = getMousePosition(diagram, event); - var componentSelected = false; - - //First check if any component if selected - for(var i = 0, len = diagram.components.length; i < len; i++) - { - if(diagram.components[i].selected) - { - diagram.endx = closestGridPoint(diagram.gridStep, mpos.x); - diagram.components[i].x += (diagram.endx - diagram.startx); - diagram.startx = diagram.endx; - diagram.endy = closestGridPoint(diagram.gridStep, mpos.y); - diagram.components[i].y += (diagram.endy - diagram.starty); - diagram.starty = diagram.endy; - diagram.paint(); - componentSelected = true; - } - } - - if(!componentSelected) - { - for(var i = 0, len = diagram.components.length; i < len; i++) - { - if(diagram.components[i].isInside(mpos.x, mpos.y)) - diagram.components[i].selectable = true; - else - diagram.components[i].selectable = false; - //Repaint only once, on a mouse enter or mouse leave - if(diagram.components[i].previousSelectable != diagram.components[i].selectable) - { - diagram.components[i].previousSelectable = diagram.components[i].selectable; - diagram.paint(); - } - } - } - - return false; - } - - function diagramMouseUp(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - var mpos = getMousePosition(diagram, event); - - for(var i = 0, len = diagram.components.length; i < len; i++) - { - //Unselect all - diagram.components[i].selected = false; - } - diagram.startx = 0; - diagram.endx = diagram.startx; - diagram.starty = 0; - diagram.endx = diagram.starty; - - return false; - } - - function diagramDoubleClick(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - - alert(diagram.toString()); - - return false; - } - - function copyPrototype(descendant, parent) - { - var sConstructor = parent.toString(); - var aMatch = sConstructor.match(/\s*function (.*)\(/); - if(aMatch != null) - { - descendant.prototype[aMatch[1]] = parent; - } - for(var m in parent.prototype) - { - descendant.prototype[m] = parent.prototype[m]; - } - } - - function Diagram(element, frozen) - { - this.element = element; - this.frozen = frozen; - this.canvas = element[0]; - this.canvas.diagram = this; - this.width = this.canvas.width; - this.height = this.canvas.height; - this.ctx = this.canvas.getContext("2d"); - this.background = Color.black; - if (!this.frozen) - { - this.canvas.addEventListener('mousedown', diagramMouseDown, false); - this.canvas.addEventListener('mousemove', diagramMouseMove, false); - this.canvas.addEventListener('mouseup', diagramMouseUp, false); - this.canvas.addEventListener('dblclick', diagramDoubleClick, false); - } - //To disable text selection outside the canvas - this.canvas.onselectstart = function(){return false;}; - this.components = []; - this.gridStep = 5; - this.startx = 0; - this.endx = 0; - this.starty = 0; - this.endy = 0; - this.showGrid = false; - this.xGridMin = 10; - this.xGridMax = 500; - this.yGridMin = 10; - this.yGridMax = 500; - this.xOrigin = 0; - this.yOrigin = 0; - this.scale = 2; //Scaling is the same in x and y directions - this.fontSize = 6; - this.fontType = 'sans-serif'; - } - - Diagram.prototype.toString = function() - { - var result = ""; - for(var i = 0, len = this.components.length; i < len; i++) - { - result += this.components[i].toString(); - } - - return result; - } - - Diagram.prototype.addNode = function(x, y) - { - var n = new Node(x, y); - n.ctx = this.ctx; - n.diagram = this; - n.updateBoundingBox(); - this.components.push(n); - return n; - } - - Diagram.prototype.addWire = function(x1, y1, x2, y2) - { - var w = new Wire(x1, y1, x2, y2) - w.ctx = this.ctx; - w.diagram = this; - w.updateBoundingBox(); - this.components.push(w); - return w; - } - - Diagram.prototype.addLabel = function(x, y, value, textAlign) - { - var l = new Label(x, y, value, textAlign) - l.ctx = this.ctx; - l.diagram = this; - l.updateBoundingBox(); - this.components.push(l); - return l; - } - - Diagram.prototype.addResistor = function(x, y, value) - { - var r = new Resistor(x, y, value) - r.ctx = this.ctx; - r.diagram = this; - r.updateBoundingBox(); - this.components.push(r); - return r; - } - - Diagram.prototype.addInductor = function(x, y, value) - { - var l = new Inductor(x, y, value) - l.ctx = this.ctx; - l.diagram = this; - l.updateBoundingBox(); - this.components.push(l); - return l; - } - - Diagram.prototype.addCapacitor = function(x, y, value) - { - var c = new Capacitor(x, y, value) - c.ctx = this.ctx; - c.diagram = this; - c.updateBoundingBox(); - this.components.push(c); - return c; - } - - Diagram.prototype.addMosfet = function(x, y, value, type) - { - var m = new Mosfet(x, y, value, type) - m.ctx = this.ctx; - m.diagram = this; - m.updateBoundingBox(); - this.components.push(m); - return m; - } - - Diagram.prototype.addGround = function(x, y) - { - var g = new Ground(x, y) - g.ctx = this.ctx; - g.diagram = this; - g.updateBoundingBox(); - this.components.push(g); - return g; - } - - Diagram.prototype.addDiode = function(x, y, value) - { - var d = new Diode(x, y, value) - d.ctx = this.ctx; - d.diagram = this; - d.updateBoundingBox(); - this.components.push(d); - return d; - } - - Diagram.prototype.addSource = function(x, y, value, type) - { - var v = new Source(x, y, value, type) - v.ctx = this.ctx; - v.diagram = this; - v.updateBoundingBox(); - this.components.push(v); - return v; - } - - Diagram.prototype.paint = function() - { - this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); - if (this.showGrid) - this.drawGrid(); - - for(var i = 0, len = this.components.length; i < len; i++) - { - this.components[i].paint(); - } - } - - Diagram.prototype.drawGrid = function() - { - this.ctx.fillStyle = Color.black; - for(x = this.xGridMin; x <= this.xGridMax; x += this.gridStep) - { - for( y = this.yGridMin; y <= this.yGridMax; y += this.gridStep) - { - this.drawPixel(this.ctx, x, y); - } - } - } - //Drawing routines from schematic - Diagram.prototype.drawLine = function(c, x1, y1, x2, y2) - { - c.beginPath(); - c.moveTo((x1 - this.xOrigin) * this.scale, (y1 - this.yOrigin) * this.scale); - c.lineTo((x2 - this.xOrigin) * this.scale, (y2 - this.yOrigin) * this.scale); - c.stroke(); - } - - Diagram.prototype.drawArc = function(c, x, y, radius,startRadians, endRadians, anticlockwise, width, filled) - { - c.lineWidth = width; - c.beginPath(); - c.arc((x - this.xOrigin)*this.scale, (y - this.yOrigin)*this.scale, radius*this.scale, startRadians, endRadians, anticlockwise); - if (filled) c.fill(); - else c.stroke(); - } - - Diagram.prototype.drawCircle = function(c, x, y, radius, filled) - { - this.drawArc(c, x, y, radius, 0, 2*Math.PI, false, 1, filled); - } - - Diagram.prototype.drawText = function(c, str, x, y) - { - c.font = this.scale*this.fontSize + "pt " + this.fontType; - c.fillText(str, (x - this.xOrigin) * this.scale, (y - this.yOrigin) * this.scale); - } - //End drawing routines - - Diagram.prototype.parseSubSuperScriptText = function(str) - { - /*var regExpSub = /_\{(.*?)\}/g; - var regExpSup = /\^\{(.*?)\}/g; - var subs = []; - var sups = []; - var text = []; - var finalText = []; - var isSub = false; - var isSup = false; - - subs = str.match(regExpSub); - for (var i = 0; i < subs.length; i++) - { - subs[i] = subs[i].substring(2, subs[i].length - 1); //Discard _{ and } - } - - sups = str.match(regExpSup); - for (var i = 0; i < sups.length; i++) - { - sups[i] = sups[i].substring(2, sups[i].length - 1); //Discard ^{ and } - }*/ - - var len = str.length; - var i = 0; - var start; - var end; - found = false; - var text = []; - var type; - var ntext = ""; - - while (i < len) - { - if (str[i] == "_") //Encountered a potential subscript _ - type = "sub"; - else if (str[i] == "^") //Encountered a potential superscript ^ - type = "sup"; - - if (type == "sub" || type == "sup") - { - if (str[i+1] == "{") - { - i += 2; //Discard _{ or ^{ - start = i; - found = false; - while (i < len) //Look for } - { - if (str[i] == "}") - { - found = true; - end = i; - break; - } - i++; - } - if (found && end > start) //Discard empty subscript ie _{} - { - //Store previous normal text if not empty and tag it as so - if (ntext.length != 0) - { - text.push({s: ntext, type: "normal"}); - ntext = ""; - } - //Store subscript or superscript and tag it as so - if (type == "sub") - text.push({s: str.substring(start, end), type: "sub"}); - else if (type == "sup") - text.push({s: str.substring(start, end), type: "sup"}); - i = end + 1; - } - else - i = start - 2; //Nothing was found, backtrack to _ or ^ - } - } - ntext += str[i]; - if (i == len - 1 && ntext.length != 0) //We've reached the end, store normal text if not empty and tag it as so - text.push({s: ntext, type: "normal"}); - i++; - } - - return text; - } - - Diagram.prototype.subSuperScriptLength = function(c, text) - { - var fontNormal = this.scale*this.fontSize + "pt " + this.fontType; - var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType; - - var xpos = 0; - - for (var i = 0; i < text.length; i++) - { - if (text[i].type == "normal") - c.font = fontNormal; - else if (text[i].type == "sub") - c.font = fontSubSup; - else - c.font = fontSubSup; - xpos += c.measureText(text[i].s).width; - } - - return xpos; - } - - Diagram.prototype.drawSubSuperScript = function(c, str, x, y, way) - { - var fontNormal = this.scale*this.fontSize + "pt " + this.fontType; - var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType; - - var text = this.parseSubSuperScriptText(str); - var len = this.subSuperScriptLength(c, text); - var xposIni = (x - this.xOrigin) * this.scale; - var yposIni = (y - this.yOrigin) * this.scale; - var xpos, ypos; - - if (way == "left") - xpos = xposIni; - else if (way == "right") - xpos = xposIni - len; - else if (way == "center") - xpos = xposIni - len/2; - - //Draw the text - for (var i = 0; i < text.length; i++) - { - if (text[i].type == "normal") - { - c.font = fontNormal; - ypos = yposIni; - } - else if (text[i].type == "sub") - { - c.font = fontSubSup; - ypos = yposIni + 3; - } - else - { - c.font = fontSubSup; - ypos = yposIni - 5; - } - c.fillText(text[i].s, xpos, ypos); - //Advance x position - xpos += c.measureText(text[i].s).width; - } - } - - //Draws a rectangle, top left corner x1, y1 and bottom right corner x2, y2 - Diagram.prototype.drawCrispLine = function(c, x1, y1, x2, y2) - { - c.beginPath(); - c.moveTo(x1 + 0.5, y1 + 0.5); - c.lineTo(x2 + 0.5, y2 + 0.5); - c.stroke(); - } - - Diagram.prototype.drawRect = function(c, x1, y1, x2, y2) - { - c.strokeRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0); - } - - Diagram.prototype.fillRect = function(c, x1, y1, x2, y2) - { - c.fillRect(x1, y1, x2 - x1 + 1.0, y2 - y1 + 1.0); - } - - Diagram.prototype.clearRect = function(c, x1, y1, x2, y2) - { - c.clearRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0); - } - - Diagram.prototype.drawPixel = function(c, x, y) - { - c.fillRect(x, y, 1.0, 1.0); - } - - Diagram.prototype.drawPoint = function(c, x, y, radius) - { - c.beginPath(); - c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise - c.fill(); - } - - Diagram.prototype.drawHollowPoint = function(c, x, y, radius) - { - c.beginPath(); - c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise - c.stroke(); - } - - Diagram.prototype.drawTriangle = function(c, x1, y1, x2, y2, x3, y3) - { - c.beginPath(); - c.moveTo(x1 + 0.5, y1 + 0.5); - c.lineTo(x2 + 0.5, y2 + 0.5); - c.lineTo(x3 + 0.5, y3 + 0.5); - c.closePath(); - c.stroke(); - } - - Diagram.prototype.fillTriangle = function(c, x1, y1, x2, y2, x3, y3) - { - c.beginPath(); - c.moveTo(x1 + 0.5, y1 + 0.5); - c.lineTo(x2 + 0.5, y2 + 0.5); - c.lineTo(x3 + 0.5, y3 + 0.5); - c.closePath(); - c.fill(); - } - - Diagram.prototype.drawHalfCircle = function(c, x, y, radius, concaveDown) //For inductance only - { - c.beginPath(); - if (concaveDown) - c.arc(x + 0.5, y + 0.5, radius, 0, Math.PI, true); //Last param is anticlockwise - else - c.arc(x + 0.5, y + 0.5, radius, Math.PI, 0, true); //Last param is anticlockwise - c.stroke(); - } - - Diagram.prototype.drawDiamond = function(c, x, y, h) - { - var xc = x + 0.5; - var yc = y + 0.5; - - c.beginPath(); - c.moveTo(xc-h, yc); - c.lineTo(xc, yc-h); - c.lineTo(xc+h, yc); - c.lineTo(xc, yc+h); - c.closePath(); - - c.fill(); - } - - Diagram.prototype.drawX = function(c, x, y, h) - { - var xc = x + 0.5; - var yc = y + 0.5; - - c.beginPath(); - c.moveTo(xc+h, yc-h); - c.lineTo(xc-h, yc+h); - c.moveTo(xc-h, yc-h); - c.lineTo(xc+h, yc+h); - c.stroke(); - } - - Diagram.prototype.drawArrow = function(c, x1, y1, x2, y2, base, height) - { - var xs1 = x1 + 0.5; - var ys1 = y1 + 0.5; - var xs2 = x2 + 0.5; - var ys2 = y2 + 0.5; - var xv = x2 - x1; - var yv = y2 - y1; - var ang = Math.atan2(-yv, xv); - - c.beginPath(); - //Arrow line - c.moveTo(xs1, ys1); - c.lineTo(xs2, ys2); - c.stroke(); - //Arrow head, first draw a triangle with top on origin then translate/rotate to orient and fit on line - c.save(); - c.beginPath(); - c.translate(xs2, ys2); - c.rotate(Utils.PI_DIV_2-ang); - - c.moveTo(0, 0); - c.lineTo(-base, height); - c.lineTo(base, height); - c.closePath(); - c.fill(); - //c.stroke(); - c.restore(); - } - - //***** COMPONENT *****// - function Component(x, y, width, height) - { - this.x = x; - this.y = y; - - this.boundingBox = [0, 0, 0, 0]; - this.transBoundingBox = [0, 0, 0, 0]; - this.xMiddle = 0; - this.yMiddle = 0; - - this.previousSelectable = false; - this.selectable = false; - this.selected = false; - this.ctx; - this.diagram; - this.color = Color.white; - this.selectedColor = Color.red; - this.eventListeners = {}; - //Label to the left - this.label = {str: "", x: 0, y: 0, position: "left", show: true, color: Color.white}; //color: Color.lodarkgray - //String representing value to the right - this.valueString = {x: 0, y: 0, position: "right", show: true, suffix: "", decimal: -1, color: Color.white}; //color: Color.lodarkgray - - this.lineWidth = 1; - this.rotation = 0; - this.value = 0; - } - - Component.prototype.addEventListener = function(type, eventListener) - { - if(!(type in this.eventListeners)) - this.eventListeners[type] = eventListener; - } - - Component.prototype.removeEventListener = function(type, eventListener) - { - for(var i in this.eventListeners) - { - if(this.eventListeners[i] === eventListener) - delete this.eventListeners[i].eventListener; - } - } - - Component.prototype.fireEvent = function(event) - { - if( typeof event == "string") - (this.eventListeners[event])(); - else - throw new Error("Event object missing 'type' property."); - } - - Component.prototype.updateBoundingBox = function() - { - //Apply global transform - this.transBoundingBox[0] = (this.boundingBox[0] - this.diagram.xOrigin) * this.diagram.scale; - this.transBoundingBox[1] = (this.boundingBox[1] - this.diagram.yOrigin) * this.diagram.scale; - this.transBoundingBox[2] = (this.boundingBox[2] - this.diagram.xOrigin) * this.diagram.scale; - this.transBoundingBox[3] = (this.boundingBox[3] - this.diagram.yOrigin) * this.diagram.scale; - //this.getMiddle(); - this.label.x = this.transBoundingBox[0]- 5; - this.label.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2; - this.valueString.x = this.transBoundingBox[2] + 5; - this.valueString.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2; - } - - Component.prototype.initPaint = function() - { - if(this.selectable) - { - this.ctx.strokeStyle = this.selectedColor; - this.ctx.fillStyle = this.selectedColor; - } - else - { - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - } - } - - Component.prototype.transform = function() - { - this.ctx.translate(this.x, this.y); - if(this.rotation != 0) - this.ctx.rotate(-this.rotation); - } - - Component.prototype.getMiddle = function() - { - this.xMiddle = (this.boundingBox[2] - this.boundingBox[0]) / 2; - this.yMiddle = (this.boundingBox[3] - this.boundingBox[1]) / 2; - } - - Component.prototype.drawLabel = function() - { - if (this.label.show) - { - var textAlign; - this.ctx.save(); - this.ctx.fillStyle = this.label.color; - this.ctx.textAlign = "left"; - if (this.rotation == 0) //Component is vertical - { - if (this.label.position == "left") //Label is on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - else if (this.label.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - } - else if (this.rotation == Math.PI/2) //Component is horizontal - { - if (this.label.position == "left") //Label now on bottom - { - this.ctx.textBaseline = "top"; - textAlign = "center"; - } - else if (this.label.position == "right") //Label on top - { - this.ctx.textBaseline = "bottom"; - textAlign = "center"; - } - } - else if (this.rotation == Math.PI) //Component is horizontal - { - if (this.label.position == "left") //Label now on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.label.position == "right") //Label now on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - else if (this.rotation == 2*Math.PI/3) //Component is vertical - { - if (this.label.position == "left") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.label.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - this.ctx.translate(this.label.x, this.label.y); - this.ctx.rotate(this.rotation); - this.diagram.drawSubSuperScript(this.ctx, this.label.str, 0, 0, textAlign); - this.ctx.restore(); - } - } - - Component.prototype.drawValueString = function() - { - if (this.valueString.show) - { - var textAlign; - this.ctx.save(); - this.ctx.fillStyle = this.valueString.color; - this.ctx.textAlign = "left"; - if (this.rotation == 0) //Component is vertical - { - if (this.valueString.position == "left") //Label is on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - else if (this.valueString.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - } - else if (this.rotation == Math.PI/2) //Component is horizontal - { - if (this.valueString.position == "left") //Label now on bottom - { - this.ctx.textBaseline = "top"; - textAlign = "center"; - } - else if (this.valueString.position == "right") //Label on top - { - this.ctx.textBaseline = "bottom"; - textAlign = "center"; - } - } - else if (this.rotation == Math.PI) //Component is horizontal - { - if (this.valueString.position == "left") //Label now on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.valueString.position == "right") //Label now on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - else if (this.rotation == 2*Math.PI/3) //Component is vertical - { - if (this.valueString.position == "left") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.valueString.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - this.ctx.translate(this.valueString.x, this.valueString.y); - this.ctx.rotate(this.rotation); - var str; - if (this.valueString.decimal < 0) - str = this.value + " " + this.valueString.suffix; - else //Force a certain number of digits - str = (this.value).toFixed(this.valueString.decimal) + " " + this.valueString.suffix; - - this.diagram.drawSubSuperScript(this.ctx, str, 0, 0, textAlign); - this.ctx.restore(); - } - } - - Component.prototype.isInside = function(x, y) - { - var pt = transform(x, y, this.x, this.y, this.rotation); - if((this.transBoundingBox[0] <= pt.x) && (pt.x <= this.transBoundingBox[2]) && (this.transBoundingBox[1] <= pt.y) && (pt.y <= this.transBoundingBox[3])) - return true; - else - return false; - } - - //***** NODE COMPONENT *****// - function Node(x, y) - { - //Call super class - this.Component(x, y); - this.boundingBox = [-2, -2, 2, 2]; - this.nodeRadius = 2; - } - - copyPrototype(Node, Component); - Node.prototype.paint = function() - { - this.initPaint(); - this.ctx.save(); - this.transform(); - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - this.diagram.drawCircle(this.ctx, 0, 0, this.nodeRadius, true); - this.drawLabel(); - this.ctx.restore(); - } - - Node.prototype.toString = function() - { - return ""; - } - - //***** WIRE COMPONENT *****// - function Wire(x1, y1, x2, y2) - { - //Call super class - this.Component(x1, y1); - this.dx = x2 - x1; - this.dy = y2 - y1; - this.boundingBox = [-5, -5, this.dx + 5, this.dy + 5]; - } - - copyPrototype(Wire, Component); - Wire.prototype.paint = function() - { - this.initPaint(); - this.ctx.save(); - this.transform(); - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - this.diagram.drawLine(this.ctx, 0, 0, this.dx, this.dy); - this.ctx.restore(); - } - - Wire.prototype.toString = function() - { - return ""; - } - - //***** LABEL *****// - function Label(x, y, value, textAlign) - { - //Call super class - this.Component(x, y); - this.boundingBox = [-10, -10, 10, 10]; - this.value = value; - this.textAlign = textAlign; - } - - copyPrototype(Label, Component); - Label.prototype.paint = function() - { - this.ctx.save(); - this.ctx.textAlign = "left"; - this.ctx.translate(this.x, this.y); - this.ctx.rotate(this.rotation); - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - this.diagram.drawSubSuperScript(this.ctx, this.value, 0, 0, this.textAlign); - this.ctx.restore(); - } - - Label.prototype.toString = function() - { - return "
From 31dd119e79fe3de694e3719302449a1272592155 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 9 Sep 2012 17:50:11 -0400 Subject: [PATCH 20/48] settings.DATABASE_FOR_PSYCHOMETRICS overrides db for psychometrics --- .../management/commands/init_psychometrics.py | 8 ++++++-- lms/djangoapps/psychometrics/psychoanalyze.py | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py b/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py index b7c9779d08..5e782df595 100644 --- a/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py +++ b/lms/djangoapps/psychometrics/management/commands/init_psychometrics.py @@ -11,10 +11,14 @@ from track.models import * from psychometrics.models import * from xmodule.modulestore import Location +from django.conf import settings from django.core.management.base import BaseCommand #db = "ocwtutor" # for debugging -db = "default" +#db = "default" + +db = getattr(settings,'DATABASE_FOR_PSYCHOMETRICS','default') + class Command(BaseCommand): help = "initialize PsychometricData tables from StudentModule instances (and tracking data, if in SQL)." @@ -56,7 +60,7 @@ class Command(BaseCommand): tset = tset.filter(event_source='server') tset = tset.filter(event__contains="'%s'" % url) checktimes = [x.dtcreated for x in tset] - pmd.checktimes = json.dumps(checktimes) + pmd.checktimes = checktimes if not len(checktimes)==pmd.attempts: print "Oops, mismatch in number of attempts and check times for %s" % pmd diff --git a/lms/djangoapps/psychometrics/psychoanalyze.py b/lms/djangoapps/psychometrics/psychoanalyze.py index e8dd7b4684..c798c73609 100644 --- a/lms/djangoapps/psychometrics/psychoanalyze.py +++ b/lms/djangoapps/psychometrics/psychoanalyze.py @@ -12,6 +12,7 @@ import math import numpy as np from scipy.optimize import curve_fit +from django.conf import settings from django.db.models import Sum, Max from psychometrics.models import * from xmodule.modulestore import Location @@ -19,7 +20,9 @@ from xmodule.modulestore import Location log = logging.getLogger("mitx.psychometrics") #db = "ocwtutor" # for debugging -db = "default" +#db = "default" + +db = getattr(settings,'DATABASE_FOR_PSYCHOMETRICS','default') #----------------------------------------------------------------------------- # fit functions From ccbbb5a99388ace2f1295cb46125bb4a095e4f4f Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 9 Sep 2012 19:39:11 -0400 Subject: [PATCH 21/48] commit_id may be specified for modulestore reload - for multi-worker configs also show pid in migration and instructor dashboard views for thread debugging --- lms/djangoapps/instructor/views.py | 1 + lms/djangoapps/lms_migration/migrate.py | 38 ++++++++++++++++--- .../courseware/instructor_dashboard.html | 2 + lms/urls.py | 1 + 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index 74f186b689..f78a1160de 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -225,6 +225,7 @@ def instructor_dashboard(request, course_id): 'problems': problems, # psychometrics 'plots': plots, # psychometrics 'course_errors': modulestore().get_item_errors(course.location), + 'djangopid' : os.getpid(), } return render_to_response('courseware/instructor_dashboard.html', context) diff --git a/lms/djangoapps/lms_migration/migrate.py b/lms/djangoapps/lms_migration/migrate.py index a3a1c595be..403063f983 100644 --- a/lms/djangoapps/lms_migration/migrate.py +++ b/lms/djangoapps/lms_migration/migrate.py @@ -35,7 +35,17 @@ def getip(request): ip = request.META.get('REMOTE_ADDR','None') return ip -def manage_modulestores(request,reload_dir=None): + +def get_commit_id(course): + return course.metadata.get('GIT_COMMIT_ID','No commit id') + # getattr(def_ms.courses[reload_dir], 'GIT_COMMIT_ID','No commit id') + + +def set_commit_id(course,commit_id): + course.metadata['GIT_COMMIT_ID'] = commit_id + # setattr(def_ms.courses[reload_dir], 'GIT_COMMIT_ID', new_commit_id) + +def manage_modulestores(request, reload_dir=None, commit_id=None): ''' Manage the static in-memory modulestores. @@ -52,8 +62,9 @@ def manage_modulestores(request,reload_dir=None): ip = getip(request) if LOCAL_DEBUG: - html += '

IP address: %s ' % ip - html += '

User: %s ' % request.user + html += '

IP address: %s

' % ip + html += '

User: %s

' % request.user + html += '

My pid: %s

' % os.getpid() log.debug('request from ip=%s, user=%s' % (ip,request.user)) if not (ip in ALLOWED_IPS or 'any' in ALLOWED_IPS): @@ -66,14 +77,27 @@ def manage_modulestores(request,reload_dir=None): return HttpResponse(html, status=403) #---------------------------------------- - # reload course if specified + # reload course if specified; handle optional commit_id if reload_dir is not None: if reload_dir not in def_ms.courses: html += '

Error: "%s" is not a valid course directory

' % reload_dir else: - html += '

Reloaded course directory "%s"

' % reload_dir - def_ms.try_load_course(reload_dir) + # reloading based on commit_id is needed when running mutiple worker threads, + # so that a given thread doesn't reload the same commit multiple times + current_commit_id = get_commit_id(def_ms.courses[reload_dir]) + log.debug('commit_id="%s"' % commit_id) + log.debug('current_commit_id="%s"' % current_commit_id) + + if (commit_id is not None) and (commit_id==current_commit_id): + html += "

Already at commit id %s for %s

" % (commit_id, reload_dir) + else: + html += '

Reloaded course directory "%s"

' % reload_dir + def_ms.try_load_course(reload_dir) + gdir = settings.DATA_DIR / reload_dir + new_commit_id = os.popen('cd %s; git log -n 1 | head -1' % gdir).read().strip().split(' ')[1] + set_commit_id(def_ms.courses[reload_dir], new_commit_id) + html += '

commit_id=%s

' % new_commit_id #---------------------------------------- @@ -94,6 +118,8 @@ def manage_modulestores(request,reload_dir=None): html += '
' html += '

Course: %s (%s)

' % (course.display_name,cdir) + html += '

commit_id=%s

' % get_commit_id(course) + for field in dumpfields: data = getattr(course,field) html += '

%s

' % field diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index d73eda1ed7..e822f05f92 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -59,6 +59,8 @@ function goto( mode) Admin ] +
${djangopid}
+
diff --git a/lms/urls.py b/lms/urls.py index 8484ccc40b..49febaf84e 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -237,6 +237,7 @@ if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'): urlpatterns += ( url(r'^migrate/modules$', 'lms_migration.migrate.manage_modulestores'), url(r'^migrate/reload/(?P[^/]+)$', 'lms_migration.migrate.manage_modulestores'), + url(r'^migrate/reload/(?P[^/]+)/(?P[^/]+)$', 'lms_migration.migrate.manage_modulestores'), url(r'^gitreload$', 'lms_migration.migrate.gitreload'), url(r'^gitreload/(?P[^/]+)$', 'lms_migration.migrate.gitreload'), ) From 2f2e71e0d12c69c1f652ab65ee13ebc702ae2535 Mon Sep 17 00:00:00 2001 From: ichuang Date: Sun, 9 Sep 2012 21:08:31 -0400 Subject: [PATCH 22/48] add tracking log entries for modulestore reload --- lms/djangoapps/lms_migration/migrate.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lms/djangoapps/lms_migration/migrate.py b/lms/djangoapps/lms_migration/migrate.py index 403063f983..ecde31d6dd 100644 --- a/lms/djangoapps/lms_migration/migrate.py +++ b/lms/djangoapps/lms_migration/migrate.py @@ -91,6 +91,12 @@ def manage_modulestores(request, reload_dir=None, commit_id=None): if (commit_id is not None) and (commit_id==current_commit_id): html += "

Already at commit id %s for %s

" % (commit_id, reload_dir) + track.views.server_track(request, + 'reload %s skipped already at %s (pid=%s)' % (reload_dir, + commit_id, + os.getpid(), + ), + {}, page='migrate') else: html += '

Reloaded course directory "%s"

' % reload_dir def_ms.try_load_course(reload_dir) @@ -98,6 +104,9 @@ def manage_modulestores(request, reload_dir=None, commit_id=None): new_commit_id = os.popen('cd %s; git log -n 1 | head -1' % gdir).read().strip().split(' ')[1] set_commit_id(def_ms.courses[reload_dir], new_commit_id) html += '

commit_id=%s

' % new_commit_id + track.views.server_track(request, 'reloaded %s now at %s (pid=%s)' % (reload_dir, + new_commit_id, + os.getpid()), {}, page='migrate') #---------------------------------------- From bff8cd4e4ee96e09c74d9faf2c20b6c9ee790831 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 10 Sep 2012 10:10:25 -0400 Subject: [PATCH 23/48] don't break courseware if S3 is unreachable --- common/lib/xmodule/xmodule/course_module.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index f00a22782c..4a009dcae4 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -99,7 +99,14 @@ class CourseDescriptor(SequenceDescriptor): def definition_from_xml(cls, xml_object, system): textbooks = [] for textbook in xml_object.findall("textbook"): - textbooks.append(cls.Textbook.from_xml_object(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() xml_object.remove(textbook) #Load the wiki tag if it exists From 814cd560344f38a9394c604b761c02e68ec50900 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 10 Sep 2012 10:38:17 -0400 Subject: [PATCH 24/48] return proper error codes for 404 and 500 errors --- lms/djangoapps/static_template_view/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/static_template_view/views.py b/lms/djangoapps/static_template_view/views.py index 90087e06d6..8ab6216eda 100644 --- a/lms/djangoapps/static_template_view/views.py +++ b/lms/djangoapps/static_template_view/views.py @@ -6,6 +6,7 @@ from mitxmako.shortcuts import render_to_response, render_to_string from django.shortcuts import redirect from django.conf import settings +from django.http import HttpResponseNotFound, HttpResponseServerError from django_future.csrf import ensure_csrf_cookie from util.cache import cache_if_anonymous @@ -40,9 +41,9 @@ def render(request, template): def render_404(request): - return render_to_response('static_templates/404.html', {}) + return HttpResponseNotFound(render_to_string('static_templates/404.html', {})) def render_500(request): - return render_to_response('static_templates/server-error.html', {}) + return HttpResponseServerError(render_to_string('static_templates/server-error.html', {})) From 779f86569128e20d857a655a911dda61af362cf2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 10 Sep 2012 10:49:12 -0400 Subject: [PATCH 25/48] remove check-for-404 hack from tests now that we get real 404s --- lms/djangoapps/courseware/tests/tests.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 62456d65d5..f3b086748d 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -222,16 +222,9 @@ class PageLoader(ActivateLoginTestCase): handling. """ resp = self.client.get(url) - # HACK: workaround the bug that returns 200 instead of 404. - # TODO (vshnayder): once we're returning 404s, get rid of this if. - if code != 404: - self.assertEqual(resp.status_code, code) - # And 'page not found' shouldn't be in the returned page - self.assertTrue(resp.content.lower().find('page not found') == -1) - else: - # look for "page not found" instead of the status code - #print resp.content - self.assertTrue(resp.content.lower().find('page not found') != -1) + self.assertEqual(resp.status_code, code, + "got code {0} for url '{1}'. Expected code {2}" + .format(resp.status_code, url, code)) def check_pages_load(self, course_name, data_dir, modstore): From 317e2c345e028cb12c1d95b4962615cba30ecb44 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 10 Sep 2012 11:31:37 -0400 Subject: [PATCH 26/48] Add autodeploy properties task to rakefile --- rakefile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rakefile b/rakefile index 053abf56a8..9e0bbcbfa4 100644 --- a/rakefile +++ b/rakefile @@ -227,3 +227,13 @@ namespace :cms do end end end + +desc "Build a properties file used to trigger autodeploy builds" +task :autodeploy_properties do + File.open("autodeploy.properties", "w") do |file| + file.puts("UPSTREAM_NOOP=false") + file.puts("UPSTREAM_BRANCH=#{BRANCH}") + file.puts("UPSTREAM_JOB=#{PACKAGE_NAME}") + file.puts("UPSTREAM_REVISION=#{COMMIT}") + end +end From b501367ed3b6bf01cb3f49bd1d19a27ac049fba0 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 28 Aug 2012 10:32:15 -0400 Subject: [PATCH 27/48] make the test ajax_url format consistent with real code * specifically, no trailing slash --- common/lib/xmodule/xmodule/tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index 4103a7373e..654b6beb15 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -29,7 +29,7 @@ from nose.plugins.skip import SkipTest from mock import Mock i4xs = ModuleSystem( - ajax_url='/', + ajax_url='courses/course_id/modx/a_location', track_function=Mock(), get_module=Mock(), render_template=Mock(), From c1d92f9351de610984fd83ab445a9927ca66caed Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 10 Sep 2012 16:57:47 -0400 Subject: [PATCH 28/48] track psychometrics requests; add grade statistics, check for wrong value of max_grade (something not right about StudentModule.max_grade) --- lms/djangoapps/instructor/views.py | 1 + lms/djangoapps/psychometrics/psychoanalyze.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index f78a1160de..d812791c3d 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -209,6 +209,7 @@ def instructor_dashboard(request, course_id): problem = request.POST['Problem'] nmsg, plots = psychoanalyze.generate_plots_for_problem(problem) msg += nmsg + track.views.server_track(request, 'psychometrics %s' % problem, {}, page='idashboard') if idash_mode=='Psychometrics': problems = psychoanalyze.problems_with_psychometric_data(course_id) diff --git a/lms/djangoapps/psychometrics/psychoanalyze.py b/lms/djangoapps/psychometrics/psychoanalyze.py index c798c73609..bb2a6ba6a8 100644 --- a/lms/djangoapps/psychometrics/psychoanalyze.py +++ b/lms/djangoapps/psychometrics/psychoanalyze.py @@ -146,6 +146,13 @@ def generate_plots_for_problem(problem): xdat = range(1,max_attempts+1) dataset = {'xdat': xdat} + # compute grade statistics + grades = [pmd.studentmodule.grade for pmd in pmdset] + gsv = StatVar() + for g in grades: + gsv += g + msg += "

Grade distribution: %s

" % gsv + # generate grade histogram ghist = [] @@ -159,8 +166,12 @@ def generate_plots_for_problem(problem): }] }""" + if gsv.max > max_grade: + msg += "

Something is wrong: max_grade=%s, but max(grades)=%s

" % (max_grade, gsv.max) + max_grade = gsv.max + if max_grade > 1: - ghist = make_histogram([pmd.studentmodule.grade for pmd in pmdset],np.linspace(0,max_grade,max_grade+1)) + ghist = make_histogram(grades, np.linspace(0,max_grade,max_grade+1)) ghist_json = json.dumps(ghist.items()) plot = {'title': "Grade histogram for %s" % problem, @@ -192,7 +203,7 @@ def generate_plots_for_problem(problem): dtsv += dt ct0 = ct if dtsv.cnt > 2: - msg += "
time differences between checks: %s" % dtsv + msg += "

Time differences between checks: %s

" % dtsv bins = np.linspace(0,1.5*dtsv.sdv(),30) dbar = bins[1]-bins[0] thist = make_histogram(dtset,bins) @@ -223,7 +234,7 @@ def generate_plots_for_problem(problem): ylast = y + ylast yset['ydat'] = ydat - if len(ydat)>5: # try to fit to logistic function if enough data points + if len(ydat)>3: # try to fit to logistic function if enough data points cfp = curve_fit(func_2pl, xdat, ydat, [1.0, max_attempts/2.0]) yset['fitparam'] = cfp yset['fitpts'] = func_2pl(np.array(xdat),*cfp[0]) From 9266bcca6d4e6a0e5af10f227a62d122a7bfa6e0 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 10 Sep 2012 17:42:01 -0400 Subject: [PATCH 29/48] fix stupid bug in textbook handling code --- 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 4a009dcae4..7aa904205d 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -106,7 +106,7 @@ class CourseDescriptor(SequenceDescriptor): # the rest of the courseware. log.exception("Couldn't load textbook") continue - textbooks.append() + textbooks.append(txt) xml_object.remove(textbook) #Load the wiki tag if it exists From e62e58dc6eae4eb32e4730643c721ba90329cd53 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 10 Sep 2012 20:33:56 -0400 Subject: [PATCH 30/48] log attempts in tracking --- 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 8e2d12d5e9..8bf1a56404 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -507,6 +507,7 @@ class CapaModule(XModule): # 'success' will always be incorrect event_info['correct_map'] = correct_map.get_dict() event_info['success'] = success + event_info['attempts'] = self.attempts self.system.track_function('save_problem_check', event_info) if hasattr(self.system,'psychometrics_handler'): # update PsychometricsData using callback From 97a32e64eef6fcfca93876f04580fed8207e877f Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 10 Sep 2012 21:33:54 -0400 Subject: [PATCH 31/48] fix tooltip titles in seq_module for non-new-xml-format courses --- common/lib/xmodule/xmodule/seq_module.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 65f692957c..26bdd286c3 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -75,7 +75,7 @@ class SequenceModule(XModule): contents = [] for child in self.get_display_items(): progress = child.get_progress() - contents.append({ + childinfo = { 'content': child.get_html(), 'title': "\n".join( grand_child.display_name.strip() @@ -85,7 +85,10 @@ class SequenceModule(XModule): 'progress_status': Progress.to_js_status_str(progress), 'progress_detail': Progress.to_js_detail_str(progress), 'type': child.get_icon_class(), - }) + } + if childinfo['title']=='': + childinfo['title'] = child.metadata['display_name'] + contents.append(childinfo) params = {'items': contents, 'element_id': self.location.html_id(), From d8c8c85041b30a95ef16272235a242fca787fc71 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 10 Sep 2012 21:42:44 -0400 Subject: [PATCH 32/48] tabs to spaces --- common/lib/xmodule/xmodule/seq_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 26bdd286c3..e7e97626db 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -75,7 +75,7 @@ class SequenceModule(XModule): contents = [] for child in self.get_display_items(): progress = child.get_progress() - childinfo = { + childinfo = { 'content': child.get_html(), 'title': "\n".join( grand_child.display_name.strip() @@ -86,7 +86,7 @@ class SequenceModule(XModule): 'progress_detail': Progress.to_js_detail_str(progress), 'type': child.get_icon_class(), } - if childinfo['title']=='': + if childinfo['title']=='': childinfo['title'] = child.metadata['display_name'] contents.append(childinfo) From c6f794547fd77dd99fa9ad8b64ada3fdb9bfbb61 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 10 Sep 2012 21:45:38 -0400 Subject: [PATCH 33/48] more tabs -> spaces --- common/lib/xmodule/xmodule/seq_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index e7e97626db..607c1e500e 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -87,7 +87,7 @@ class SequenceModule(XModule): 'type': child.get_icon_class(), } if childinfo['title']=='': - childinfo['title'] = child.metadata['display_name'] + childinfo['title'] = child.metadata['display_name'] contents.append(childinfo) params = {'items': contents, From 56d44ec7f39fe47cebb1c6e50f555e7cb259b2a4 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 10 Sep 2012 21:55:58 -0400 Subject: [PATCH 34/48] display_name may be empty (eg in tests) --- common/lib/xmodule/xmodule/seq_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 607c1e500e..b05ea36e50 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -87,7 +87,7 @@ class SequenceModule(XModule): 'type': child.get_icon_class(), } if childinfo['title']=='': - childinfo['title'] = child.metadata['display_name'] + childinfo['title'] = child.metadata.get('display_name','') contents.append(childinfo) params = {'items': contents, From bffd9ac38d799e7adb447c018391c2734bbe9840 Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 10 Sep 2012 22:28:37 -0400 Subject: [PATCH 35/48] center histogram bars; pep8 --- lms/djangoapps/psychometrics/psychoanalyze.py | 120 ++++++++++-------- 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/lms/djangoapps/psychometrics/psychoanalyze.py b/lms/djangoapps/psychometrics/psychoanalyze.py index bb2a6ba6a8..dd7d328278 100644 --- a/lms/djangoapps/psychometrics/psychoanalyze.py +++ b/lms/djangoapps/psychometrics/psychoanalyze.py @@ -1,7 +1,7 @@ # # File: psychometrics/psychoanalyze.py # -# generate pyschometrics plots from PsychometricData +# generate pyschometrics plots from PsychometricData from __future__ import division @@ -19,98 +19,108 @@ from xmodule.modulestore import Location log = logging.getLogger("mitx.psychometrics") -#db = "ocwtutor" # for debugging +#db = "ocwtutor" # for debugging #db = "default" -db = getattr(settings,'DATABASE_FOR_PSYCHOMETRICS','default') +db = getattr(settings, 'DATABASE_FOR_PSYCHOMETRICS', 'default') #----------------------------------------------------------------------------- # fit functions -def func_2pl(x,a,b): + +def func_2pl(x, a, b): """ 2-parameter logistic function """ D = 1.7 - edax = np.exp(D*a*(x-b)) - return edax / (1+edax) + edax = np.exp(D * a * (x - b)) + return edax / (1 + edax) #----------------------------------------------------------------------------- # statistics class + class StatVar(object): """ Simple statistics on floating point numbers: avg, sdv, var, min, max """ - def __init__(self,unit=1): + def __init__(self, unit=1): self.sum = 0 self.sum2 = 0 self.cnt = 0 self.unit = unit self.min = None self.max = None - def add(self,x): + + def add(self, x): if x is None: return if self.min is None: self.min = x else: - if xself.max: + if x > self.max: self.max = x self.sum += x self.sum2 += x**2 self.cnt += 1 + def avg(self): if self.cnt is None: return 0 return self.sum / 1.0 / self.cnt / self.unit + def var(self): if self.cnt is None: return 0 return (self.sum2 / 1.0 / self.cnt / (self.unit**2)) - (self.avg()**2) + def sdv(self): v = self.var() if v>0: return math.sqrt(v) else: return 0 + def __str__(self): - return 'cnt=%d, avg=%f, sdv=%f' % (self.cnt,self.avg(),self.sdv()) - def __add__(self,x): + return 'cnt=%d, avg=%f, sdv=%f' % (self.cnt, self.avg(), self.sdv()) + + def __add__(self, x): self.add(x) return self #----------------------------------------------------------------------------- # histogram generator -def make_histogram(ydata,bins=None): + +def make_histogram(ydata, bins=None): ''' Generate histogram of ydata using bins provided, or by default bins from 0 to 100 by 10. bins should be ordered in increasing order. - + returns dict with keys being bins, and values being counts. special: hist['bins'] = bins ''' if bins is None: - bins = range(0,100,10) - + bins = range(0, 100, 10) + nbins = len(bins) - hist = dict(zip(bins,[0] * nbins)) + hist = dict(zip(bins, [0] * nbins)) for y in ydata: - for b in bins[::-1]: # in reverse order + for b in bins[::-1]: # in reverse order if y>b: hist[b] += 1 break # hist['bins'] = bins return hist - + #----------------------------------------------------------------------------- + def problems_with_psychometric_data(course_id): ''' Return dict of {problems (location urls): count} for which psychometric data is available. @@ -118,36 +128,37 @@ def problems_with_psychometric_data(course_id): ''' pmdset = PsychometricData.objects.using(db).filter(studentmodule__course_id=course_id) plist = [p['studentmodule__module_state_key'] for p in pmdset.values('studentmodule__module_state_key').distinct()] - problems = dict( (p,pmdset.filter(studentmodule__module_state_key=p).count()) for p in plist ) + problems = dict( (p, pmdset.filter(studentmodule__module_state_key=p).count()) for p in plist ) return problems #----------------------------------------------------------------------------- + def generate_plots_for_problem(problem): - + pmdset = PsychometricData.objects.using(db).filter(studentmodule__module_state_key=problem) nstudents = pmdset.count() msg = "" plots = [] if nstudents < 2: - msg += "%s nstudents=%d --> skipping, too few" % (problem,nstudents) + msg += "%s nstudents=%d --> skipping, too few" % (problem, nstudents) return msg, plots max_grade = pmdset[0].studentmodule.max_grade agdat = pmdset.aggregate(Sum('attempts'), Max('attempts')) max_attempts = agdat['attempts__max'] - total_attempts = agdat['attempts__sum'] # not used yet + total_attempts = agdat['attempts__sum'] # not used yet msg += "max attempts = %d" % max_attempts - xdat = range(1,max_attempts+1) + xdat = range(1, max_attempts + 1) dataset = {'xdat': xdat} # compute grade statistics - grades = [pmd.studentmodule.grade for pmd in pmdset] + grades = [pmd.studentmodule.grade for pmd in pmdset] gsv = StatVar() for g in grades: gsv += g @@ -171,14 +182,14 @@ def generate_plots_for_problem(problem): max_grade = gsv.max if max_grade > 1: - ghist = make_histogram(grades, np.linspace(0,max_grade,max_grade+1)) + ghist = make_histogram(grades, np.linspace(0, max_grade, max_grade + 1)) ghist_json = json.dumps(ghist.items()) plot = {'title': "Grade histogram for %s" % problem, 'id': 'histogram', 'info': '', 'data': "var dhist = %s;\n" % ghist_json, - 'cmd': "[ {data: dhist, bars: { show: true }} ], %s" % axisopts, + 'cmd': '[ {data: dhist, bars: { show: true, align: "center" }} ], %s' % axisopts, } plots.append(plot) else: @@ -186,27 +197,27 @@ def generate_plots_for_problem(problem): # histogram of time differences between checks # Warning: this is inefficient - doesn't scale to large numbers of students - dtset = [] # time differences in minutes + dtset = [] # time differences in minutes dtsv = StatVar() for pmd in pmdset: try: - checktimes = eval(pmd.checktimes) # update log of attempt timestamps + checktimes = eval(pmd.checktimes) # update log of attempt timestamps except: continue - if len(checktimes)<2: + if len(checktimes) < 2: continue ct0 = checktimes[0] for ct in checktimes[1:]: - dt = (ct-ct0).total_seconds()/60.0 - if dt<20: # ignore if dt too long + dt = (ct - ct0).total_seconds() / 60.0 + if dt < 20: # ignore if dt too long dtset.append(dt) dtsv += dt ct0 = ct if dtsv.cnt > 2: msg += "

Time differences between checks: %s

" % dtsv - bins = np.linspace(0,1.5*dtsv.sdv(),30) - dbar = bins[1]-bins[0] - thist = make_histogram(dtset,bins) + bins = np.linspace(0, 1.5 * dtsv.sdv(), 30) + dbar = bins[1] - bins[0] + thist = make_histogram(dtset, bins) thist_json = json.dumps(sorted(thist.items(), key=lambda(x): x[0])) axisopts = """{ xaxes: [{ axisLabel: 'Time (min)'}], yaxes: [{position: 'left',axisLabel: 'Count'}]}""" @@ -215,33 +226,33 @@ def generate_plots_for_problem(problem): 'id': 'thistogram', 'info': '', 'data': "var thist = %s;\n" % thist_json, - 'cmd': "[ {data: thist, bars: { show: true, barWidth:%f }} ], %s" % (dbar, axisopts), + 'cmd': '[ {data: thist, bars: { show: true, align: "center", barWidth:%f }} ], %s' % (dbar, axisopts), } plots.append(plot) # one IRT plot curve for each grade received (TODO: this assumes integer grades) - for grade in range(1,int(max_grade)+1): + for grade in range(1, int(max_grade) + 1): yset = {} gset = pmdset.filter(studentmodule__grade=grade) ngset = gset.count() - if ngset==0: + if ngset == 0: continue ydat = [] ylast = 0 for x in xdat: - y = gset.filter(attempts=x).count()/ngset + y = gset.filter(attempts=x).count() / ngset ydat.append( y + ylast ) ylast = y + ylast yset['ydat'] = ydat - if len(ydat)>3: # try to fit to logistic function if enough data points - cfp = curve_fit(func_2pl, xdat, ydat, [1.0, max_attempts/2.0]) + if len(ydat) > 3: # try to fit to logistic function if enough data points + cfp = curve_fit(func_2pl, xdat, ydat, [1.0, max_attempts / 2.0]) yset['fitparam'] = cfp - yset['fitpts'] = func_2pl(np.array(xdat),*cfp[0]) - yset['fiterr'] = [yd-yf for (yd,yf) in zip(ydat,yset['fitpts'])] - fitx = np.linspace(xdat[0],xdat[-1],100) + yset['fitpts'] = func_2pl(np.array(xdat), *cfp[0]) + yset['fiterr'] = [yd - yf for (yd, yf) in zip(ydat, yset['fitpts'])] + fitx = np.linspace(xdat[0], xdat[-1], 100) yset['fitx'] = fitx - yset['fity'] = func_2pl(np.array(fitx),*cfp[0]) + yset['fity'] = func_2pl(np.array(fitx), *cfp[0]) dataset['grade_%d' % grade] = yset @@ -257,27 +268,27 @@ def generate_plots_for_problem(problem): }""" # generate points for flot plot - for grade in range(1,int(max_grade)+1): + for grade in range(1, int(max_grade) + 1): jsdata = "" jsplots = [] gkey = 'grade_%d' % grade if gkey in dataset: yset = dataset[gkey] - jsdata += "var d%d = %s;\n" % (grade,json.dumps(zip(xdat,yset['ydat']))) + jsdata += "var d%d = %s;\n" % (grade, json.dumps(zip(xdat, yset['ydat']))) jsplots.append('{ data: d%d, lines: { show: false }, points: { show: true}, color: "red" }' % grade) if 'fitpts' in yset: - jsdata += 'var fit = %s;\n' % (json.dumps(zip(yset['fitx'],yset['fity']))) + jsdata += 'var fit = %s;\n' % (json.dumps(zip(yset['fitx'], yset['fity']))) jsplots.append('{ data: fit, lines: { show: true }, color: "blue" }') - (a,b) = yset['fitparam'][0] - irtinfo = "(2PL: D=1.7, a=%6.3f, b=%6.3f)" % (a,b) + (a, b) = yset['fitparam'][0] + irtinfo = "(2PL: D=1.7, a=%6.3f, b=%6.3f)" % (a, b) else: irtinfo = "" - plots.append({'title': 'IRT Plot for grade=%s %s' % (grade,irtinfo), + plots.append({'title': 'IRT Plot for grade=%s %s' % (grade, irtinfo), 'id': "irt%s" % grade, 'info': '', 'data': jsdata, - 'cmd' : '[%s], %s' % (','.join(jsplots), axisopts), + 'cmd': '[%s], %s' % (','.join(jsplots), axisopts), }) #log.debug('plots = %s' % plots) @@ -285,6 +296,7 @@ def generate_plots_for_problem(problem): #----------------------------------------------------------------------------- + def make_psychometrics_data_update_handler(studentmodule): """ Construct and return a procedure which may be called to update @@ -307,13 +319,13 @@ def make_psychometrics_data_update_handler(studentmodule): state = json.loads(sm.state) done = state['done'] except: - log.exception("Oops, failed to eval state for %s (state=%s)" % (sm,sm.state)) + log.exception("Oops, failed to eval state for %s (state=%s)" % (sm, sm.state)) return pmd.done = done pmd.attempts = state['attempts'] try: - checktimes = eval(pmd.checktimes) # update log of attempt timestamps + checktimes = eval(pmd.checktimes) # update log of attempt timestamps except: checktimes = [] checktimes.append(datetime.datetime.now()) From 3fd640aef326f96241384e339f79f767748d5db1 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Tue, 11 Sep 2012 10:22:03 -0400 Subject: [PATCH 36/48] Removed display none for histogram --- lms/static/sass/course/courseware/_courseware.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index 0532f04b42..863bcad139 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -80,7 +80,6 @@ div.course-wrapper { } .histogram { - display: none; width: 200px; height: 150px; } From 064b5e793252c2afb7cc2f2a1722ab0f26f9ca55 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Tue, 11 Sep 2012 11:25:54 -0400 Subject: [PATCH 37/48] Remove widows in accordion header --- lms/templates/courseware/courseware.html | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index 29b9be3789..a433ddc9fc 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -12,6 +12,7 @@ + ## codemirror @@ -21,6 +22,7 @@ ## ## + <%static:js group='courseware'/> <%static:js group='discussion'/> @@ -35,6 +37,22 @@ From 4732e39cb4d0da12c28f6078bb950a089798ffb0 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Tue, 11 Sep 2012 12:09:42 -0400 Subject: [PATCH 38/48] info-title margin fixed --- lms/static/sass/course/_info.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index e25bb9d8c4..80db054afd 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -25,11 +25,6 @@ div.info-wrapper { margin-bottom: lh(); padding-bottom: lh(.5); - &:first-child { - margin: 0 (-(lh(.5))) lh(); - padding: lh(.5); - } - ol, ul { margin: 0; list-style-type: disk; From 7a248f0154bdba45874dba0cec5c6b4baa2f3b1f Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Tue, 11 Sep 2012 14:05:07 -0400 Subject: [PATCH 39/48] Added fixes to some style and one Cale fix to get things working properly --- cms/djangoapps/contentstore/views.py | 2 +- cms/static/sass/_base.scss | 2 ++ cms/static/sass/_calendar.scss | 5 ----- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 490f49a41c..d701db33a3 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -53,7 +53,7 @@ def index(request): """ courses = modulestore().get_items(['i4x', None, None, 'course', None]) return render_to_response('index.html', { - 'courses': [(course.metadata['display_name'], + 'courses': [(course.metadata.get('display_name'), reverse('course_index', args=[ course.location.org, course.location.course, diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 410f74ee07..90a9629351 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -14,9 +14,11 @@ $yellow: #fff8af; $cream: #F6EFD4; $border-color: #ddd; + // edX colors $blue: rgb(29,157,217); $pink: rgb(182,37,104); +$error-red: rgb(253, 87, 87); @mixin hide-text { background-color: transparent; diff --git a/cms/static/sass/_calendar.scss b/cms/static/sass/_calendar.scss index 35609b2d56..4c007bb561 100644 --- a/cms/static/sass/_calendar.scss +++ b/cms/static/sass/_calendar.scss @@ -330,11 +330,6 @@ section.cal { &:hover { opacity: 1; - width: flex-grid(5) + flex-gutter(); - - + section.main-content { - width: flex-grid(7); - } } > header { From f35412f3f4e3fd8c048632db83b277014b41f011 Mon Sep 17 00:00:00 2001 From: kimth Date: Tue, 11 Sep 2012 16:29:17 -0400 Subject: [PATCH 40/48] Don't bind update_schematics to global window.onload --- common/lib/xmodule/xmodule/js/src/capa/schematic.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js index e07b98d63c..b01f6e12e8 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js +++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js @@ -2023,7 +2023,16 @@ function add_schematic_handler(other_onload) { update_schematics(); } } -window.onload = add_schematic_handler(window.onload); +/* + * THK: Attaching update_schematic to window.onload is rather presumptuous... + * The function is called for EVERY page load, whether in courseware or in + * course info, in 6.002x or the public health course. It is also redundant + * because courseware includes an explicit call to update_schematic after + * each ajax exchange. In this case, calling update_schematic twice appears + * to contribute to a bug in Firefox that does not render the schematic + * properly depending on timing. + */ +//window.onload = add_schematic_handler(window.onload); // ask each schematic input widget to update its value field for submission function prepare_schematics() { From 5e4a498cfe0745396b2f2a88c3c310a97e8bd86f Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Tue, 11 Sep 2012 15:09:49 -0700 Subject: [PATCH 41/48] Provide a reasonable seeding mechanism for jsresponses --- common/lib/capa/capa/javascript_problem_generator.js | 4 +--- common/lib/capa/capa/responsetypes.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/common/lib/capa/capa/javascript_problem_generator.js b/common/lib/capa/capa/javascript_problem_generator.js index 8c8d39b19f..1cd4616c5a 100644 --- a/common/lib/capa/capa/javascript_problem_generator.js +++ b/common/lib/capa/capa/javascript_problem_generator.js @@ -11,13 +11,11 @@ importAll("xproblem"); generatorModulePath = process.argv[2]; dependencies = JSON.parse(process.argv[3]); -seed = process.argv[4]; +seed = JSON.parse(process.argv[4]); params = JSON.parse(process.argv[5]); if(seed==null){ seed = 4; -}else{ - seed = parseInt(seed); } for(var i = 0; i < dependencies.length; i++){ diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index b803452b8c..d9216f06d6 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -408,7 +408,7 @@ class JavascriptResponse(LoncapaResponse): output = self.call_node([generator_file, self.generator, json.dumps(self.generator_dependencies), - json.dumps(str(self.system.seed)), + json.dumps(self.context['random'].getrandbits(9)), json.dumps(self.params)]).strip() return json.loads(output) From bd5fc64462449fe0085c931bb75e1227a13cd587 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Tue, 11 Sep 2012 18:08:27 -0700 Subject: [PATCH 42/48] make seeds consistent with other responsetypes --- common/lib/capa/capa/responsetypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index d9216f06d6..7f1ff32f67 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -408,7 +408,7 @@ class JavascriptResponse(LoncapaResponse): output = self.call_node([generator_file, self.generator, json.dumps(self.generator_dependencies), - json.dumps(self.context['random'].getrandbits(9)), + json.dumps(str(self.context['the_lcp'].seed)), json.dumps(self.params)]).strip() return json.loads(output) From 05c3e028c2d9b37587de4080a549ad1b72d787f7 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Wed, 12 Sep 2012 09:32:24 -0400 Subject: [PATCH 43/48] Added new press releases --- .../images/press/baltsun_logo_178x138.jpg | Bin 0 -> 3779 bytes .../images/press/bostinno_logo_178x138.jpg | Bin 0 -> 3865 bytes .../images/press/bostonmag_logo_178x138.jpg | Bin 0 -> 3441 bytes .../images/press/csmonitor_logo_178x138.jpg | Bin 0 -> 5130 bytes .../press/insidehighered_logo_178x138.jpg | Bin 0 -> 5268 bytes .../images/press/itbriefing_logo_178x138.jpg | Bin 0 -> 2904 bytes .../images/press/radioboston_logo_178x138.jpg | Bin 0 -> 5558 bytes .../images/press/smartplanet_logo_178x138.jpg | Bin 0 -> 2855 bytes .../images/press/techreview_logo_178x138.jpg | Bin 0 -> 4261 bytes .../images/press/thetech_logo_178x138.jpg | Bin 0 -> 3958 bytes lms/static/images/press/time_logo_178x138.jpg | Bin 0 -> 4067 bytes lms/templates/press.json | 183 ++++++++++++++++++ lms/templates/static_templates/press.html | 1 - 13 files changed, 183 insertions(+), 1 deletion(-) create mode 100755 lms/static/images/press/baltsun_logo_178x138.jpg create mode 100644 lms/static/images/press/bostinno_logo_178x138.jpg create mode 100755 lms/static/images/press/bostonmag_logo_178x138.jpg create mode 100755 lms/static/images/press/csmonitor_logo_178x138.jpg create mode 100755 lms/static/images/press/insidehighered_logo_178x138.jpg create mode 100755 lms/static/images/press/itbriefing_logo_178x138.jpg create mode 100755 lms/static/images/press/radioboston_logo_178x138.jpg create mode 100755 lms/static/images/press/smartplanet_logo_178x138.jpg create mode 100755 lms/static/images/press/techreview_logo_178x138.jpg create mode 100755 lms/static/images/press/thetech_logo_178x138.jpg create mode 100755 lms/static/images/press/time_logo_178x138.jpg diff --git a/lms/static/images/press/baltsun_logo_178x138.jpg b/lms/static/images/press/baltsun_logo_178x138.jpg new file mode 100755 index 0000000000000000000000000000000000000000..3dc619ac7b3caba28efcb41da9d0c22db1461106 GIT binary patch literal 3779 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 Df5!ho literal 0 HcmV?d00001 diff --git a/lms/static/images/press/bostonmag_logo_178x138.jpg b/lms/static/images/press/bostonmag_logo_178x138.jpg new file mode 100755 index 0000000000000000000000000000000000000000..aa4f8a357718b71544654bf419351a867dde4dc8 GIT binary patch literal 3441 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 OudU$!#pTY!p8N;7J~1c& literal 0 HcmV?d00001 diff --git a/lms/static/images/press/csmonitor_logo_178x138.jpg b/lms/static/images/press/csmonitor_logo_178x138.jpg new file mode 100755 index 0000000000000000000000000000000000000000..d389b7a6f11c6eea545de1f0abac49a86c8f0b68 GIT binary patch literal 5130 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 A!vFvP literal 0 HcmV?d00001 diff --git a/lms/static/images/press/insidehighered_logo_178x138.jpg b/lms/static/images/press/insidehighered_logo_178x138.jpg new file mode 100755 index 0000000000000000000000000000000000000000..ad23f11a963b5ad4064b081964dd1a5256fcb38a GIT binary patch literal 5268 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 G8T&VQH1uu& literal 0 HcmV?d00001 diff --git a/lms/static/images/press/radioboston_logo_178x138.jpg b/lms/static/images/press/radioboston_logo_178x138.jpg new file mode 100755 index 0000000000000000000000000000000000000000..e29949a31f5d718da0c097b9727d18956ab5167b GIT binary patch literal 5558 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 Ctr9x` literal 0 HcmV?d00001 diff --git a/lms/static/images/press/time_logo_178x138.jpg b/lms/static/images/press/time_logo_178x138.jpg new file mode 100755 index 0000000000000000000000000000000000000000..5d884fe0fe58b875b9c498dabcfd7d30c2bab884 GIT binary patch literal 4067 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

_zo2qY58Um@o`X6W* zOSc<(-I&KtzeX364g?dn5tb%4s^R5}(K6G~2%4VxWZog6?XSI{0|`AisO!}WrWgNu z%&Al>6dwIIv=aEAw371=t+0R~zeQTM9#To#W$mgOK5fN&!GE{fKTpa!K1ipH*28U* zE``XH?_=?yfZ#~LmOh0|CRAiLAeGE#P-cQrWkcmR6obQN+SM@( zbW?XWPpK9EfPs~(ndZVJ6*z<@^k+$j_=@@9#}JXYl&#(J)EH1x1=b&JT-S<5FTz5z z&aziM8AQ4u=it`U>9D08KzWhhU=JlkcBaK%OA+Pd(cdY#je>@~?OJrdP0as3PURTR zD&s^BYr&%|TDqDFyH>`UsU~e_Y9?^oJ7mc>==x)TOlYB)YgNY=bSx??VwLXL_`W%+ z<3+lNy(}%$G>{WjQT&DYM^Tei1Ss=R|9MZ8-uZZkgc?lpb|DC^lRciI55Z*%mLIq2g6V5JbbgD} z*{>h+e{M7qr(Vr6m*hHKiSYt7Wa$wTFMv zbx7fBn{%p<6X8(y#m6T~<-gm)>Z^B7GPQ0CEI1eB6HSktha(2pL`Fsc%c4w!<#0w$ z1ZO_lrPOXTD|4&&igSSFbJ|}#pA8-`!u1-jNExAq#;4)Z<4}i~CntZfYpfrbc3w`d zG#p7Na``RO@PiN4qh8*KxEFkVKNd}|Q7VB(Ntsj*^&<0{r;v*tshnZq_t;Ls>kES> zyKGR=bW;{O9gV{bH<33|y~~`0H|4N|#)`8;Ci^~cRH_K3gpc{<}6g25bx2>V(M z?mdNvMPLn@s!R}u9#UO4H3f82R>&_WJY)l&ylZ}zJNwE#6U*Pj{lGoeia5+1TPjV0 zM|1VfX5W$X_edOD%~8B9)q8=V6eiC1?v{|5t;5#n)XW1yg`qIa&_$=5!cP#YaItKu eWzWH7OnAzGhFVFN{Cx#=f6?gwlZEYg>c0TM(SDf# literal 0 HcmV?d00001 diff --git a/lms/templates/press.json b/lms/templates/press.json index 16942f06fb..f523f430af 100644 --- a/lms/templates/press.json +++ b/lms/templates/press.json @@ -1,4 +1,187 @@ [ + + { + "title": "Is MIT Giving Away the Farm?", + "url": "http://www.technologyreview.com/mitnews/428698/is-mit-giving-away-the-farm/", + "author": "Larry Hardesty", + "image": "techreview_logo_178x138.jpg", + "deck": "The surprising logic of MIT's free online education program.", + "publication": "Technology Review", + "publish_date": "September/October 2012" + }, + { + "title": "School’s Out, Forever", + "url": "http://www.bostonmagazine.com/articles/2012/08/edx-online-classes-schools-ou
t-forever/", + "author": "Chris Vogel", + "image": "bostonmag_logo_178x138.jpg", + "deck": "A new online education program from Harvard and MIT is poised to transform what it means to go to college.", + "publication": "Boston Magazine", + "publish_date": "September 2012" + }, + { + "title": "Q&A: Anant Agarwal, edX’s president and first professor", + "url": "http://www.bostonmagazine.com/articles/2012/08/edx-online-classes-schools-ou
t-forever/", + "author": " Molly Petrilla ", + "image": "smartplanet_logo_178x138.jpg", + "deck": "", + "publication": "Smart Planet", + "publish_date": "September 3, 2012" + }, + + { + "title": "EdX To Offer Proctored Final Exam For One Course", + "url": "http://www.thecrimson.com/article/2012/9/7/edx-offer-proctored-exams/", + "author": "Samuel Y. Weinstock", + "image": "harvardcrimson_logo_178x138.jpeg", + "deck": "", + "publication": "Harvard Crimson", + "publish_date": "September 7, 2012" + }, + { + "title": "MOOCing On Site", + "url": "http://www.insidehighered.com/news/2012/09/07/site-based-testing-deals-strengthen-case-granting-credit-mooc-students", + "author": "Steve Kolowich ", + "image": "insidehighered_logo_178x138.jpg", + "deck": "", + "publication": "Inside Higher Education", + "publish_date": "September 7, 2012" + }, + { + "title": "edX Curbs the Downfalls of Online Education By Announcing Supervised Final Exams", + "url": "http://bostinno.com/2012/09/07/edx-pearson-proctored-exams/", + "author": "Lauren Landry", + "image": "bostinno_logo_178x138.jpg", + "deck": "", + "publication": "Bostinno", + "publish_date": "September 7, 2012" + }, + { + "title": "Harvard and MIT online courses get 'real world' exams", + "url": "http://www.bbc.co.uk/news/education-19505776", + "author": "Sean Coughlan", + "image": "bbc_logo_178x138.jpeg", + "deck": "", + "publication": "BBC", + "publish_date": "September 6, 2012" + }, + { + "title": "Harvard-MIT Online School EdX to Offer Supervised Final Exams", + "url": "http://www.businessweek.com/news/2012-09-06/harvard-mit-online-school-edx-to-offer-supervised-final-exams", + "author": "Oliver Staley", + "image": "bloomberg_logo_178x138.jpeg", + "deck": "", + "publication": "Bloomberg Business Week", + "publish_date": "September 6, 2012" + }, + { + "title": "Colorado State to Offer Credits for Online Class", + "url": "http://www.nytimes.com/2012/09/07/education/colorado-state-to-offer-credits-for-online-class.html?_r=3", + "author": "Tamar Lewin", + "image": "nyt_logo_178x138.jpeg", + "deck": "", + "publication": "New York Times", + "publish_date": "September 6, 2012" + }, + { + "title": "edX Offers Proctored Exams for Open Online Course", + "url": "http://chronicle.com/blogs/wiredcampus/edx-offers-proctored-exams-for-open-online-course/39656", + "author": " Marc Parry", + "image": "chroniclehighered_logo_178x138.jpeg", + "deck": "", + "publication": "Chronicle of Higher Education", + "publish_date": "September 6, 2012" + }, + { + "title": "edX Offers Proctored Exams for Open Online Course", + "url": "http://itbriefing.net/modules.php?op=modload&name=News&file=article&sid=323229&newlang=eng&topic=15&catid=37", + "author": "", + "image": "itbriefing_logo_178x138.jpg", + "deck": "", + "publication": "ITBriefing.net", + "publish_date": "September 6, 2012" + }, + + { + "title": "Student Loans: Debt for Life", + "url": "http://www.businessweek.com/articles/2012-09-06/student-loans-debt-for-life#p3", + "author": "Peter Coy", + "image": "bloomberg_logo_178x138.jpeg", + "deck": "", + "publication": "Bloomberg Business Week", + "publish_date": "September 6, 2012" + }, + { + "title": "Straighterline wants to help professors expand reach, while students save", + "url": "http://www.baltimoresun.com/business/technology/blog/bs-bz-straighterline-college-professors-20120904,0,6114022.story", + "author": "Gus G. Sentementes", + "image": "baltsun_logo_178x138.jpg", + "deck": "", + "publication": "The Baltimore Sun", + "publish_date": "September 4, 2012" + }, + { + "title": "Want to be a reporter? Learn to code", + "url": "http://gigaom.com/cloud/want-to-be-a-reporter-learn-to-code/", + "author": "Barb Darrow", + "image": "gigaom_logo_178x138.jpeg", + "deck": "", + "publication": "GigaOM", + "publish_date": "September 4, 2012" + }, + { + "title": "MOOC Brigade: Will Massive, Open Online Courses Revolutionize Higher Education?", + "url": "http://nation.time.com/2012/09/04/mooc-brigade-will-massive-open-online-courses-revolutionize-higher-education/", + "author": "Kayla Webley", + "image": "time_logo_178x138.jpg", + "deck": "", + "publication": "Time", + "publish_date": "September 4, 2012" + }, + { + "title": "Ivy walls lower with free online classes from Coursera and edX ", + "url": "http://www.csmonitor.com/Innovation/Pioneers/2012/0903/Ivy-walls-lower-with-free-online-classes-from-Coursera-and-edX", + "author": "Chris Gaylord", + "image": "csmonitor_logo_178x138.jpg", + "deck": "", + "publication": "Christian Science Monitor", + "publish_date": "September 3, 2012" + }, + { + "title": "Summer recap. RLADs, new edX partner, Institute files amicus brief", + "url": "http://tech.mit.edu/V132/N34/summer.html", + "author": "", + "image": "thetech_logo_178x138.jpg", + "deck": "", + "publication": "The Tech", + "publish_date": "September 4, 2012" + }, + { + "title": "Into the Future With MOOC's", + "url": "http://chronicle.com/article/Into-the-Future-With-MOOCs/134080/", + "author": "Kevin Carey", + "image": "chroniclehighered_logo_178x138.jpeg", + "deck": "", + "publication": "The Chronicle of Higher Education", + "publish_date": "September 3, 2012" + }, + { + "title": "The Future Of Higher Education", + "url": "http://radioboston.wbur.org/2012/08/20/higher-education-online", + "author": "", + "image": "radioboston_logo_178x138.jpg", + "deck": "", + "publication": "NPR/Radio Boston", + "publish_date": "August 20, 2012" + }, + { + "title": "Berkeley Joins edX", + "url": "http://www.insidehighered.com/quicktakes/2012/07/24/berkeley-joins-edx", + "author": "Tamar Lewin", + "image": "insidehighered_logo_178x138.jpg", + "deck": "", + "publication": "Inside Higher Ed", + "publish_date": "July 24, 2012" + }, { "title": "Berkeley to Join the Free Online Learning Partnership EdX", "url": "http://www.nytimes.com/2012/07/24/education/berkeley-to-offer-free-online-classes-on-edx.html?_r=1", diff --git a/lms/templates/static_templates/press.html b/lms/templates/static_templates/press.html index 6294b346a9..277cb91bd2 100644 --- a/lms/templates/static_templates/press.html +++ b/lms/templates/static_templates/press.html @@ -37,4 +37,3 @@ % endfor - From 596d926eacc455815c86f095f2943b311d47d728 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Wed, 12 Sep 2012 11:06:01 -0400 Subject: [PATCH 44/48] Added fix so that sequnce nave doesn't go over full screen videos --- common/lib/xmodule/xmodule/css/sequence/display.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/css/sequence/display.scss b/common/lib/xmodule/xmodule/css/sequence/display.scss index 25d2c26dda..0533465298 100644 --- a/common/lib/xmodule/xmodule/css/sequence/display.scss +++ b/common/lib/xmodule/xmodule/css/sequence/display.scss @@ -32,7 +32,7 @@ nav.sequence-nav { .sequence-list-wrapper { position: relative; - z-index: 9999; + z-index: 99; border: 1px solid #ccc; height: 44px; margin: 0 30px; From 2f8a7d0744df7330ea6d526f06cb8c438be74365 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Wed, 12 Sep 2012 12:37:19 -0400 Subject: [PATCH 45/48] Removes unused widowfix script --- lms/templates/courseware/courseware.html | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index a433ddc9fc..67719eacc9 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -12,7 +12,6 @@ - ## codemirror From ec9521a9f543c6d03c3e30140b7340a2b90b9c41 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Wed, 12 Sep 2012 13:01:10 -0400 Subject: [PATCH 46/48] removed list bullets from tutorials --- lms/static/sass/course/courseware/_courseware.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index 863bcad139..0b79ec6a6b 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -116,6 +116,7 @@ div.course-wrapper { margin: 0; @include clearfix(); padding: 0; + list-style: none; li { width: flex-grid(3, 9); From e47134bd1097a1c703ab1065dd9d914ca8e802f5 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 12 Sep 2012 13:20:15 -0400 Subject: [PATCH 47/48] turn off psychometrics in dev.py--the necessary migration is not present --- lms/envs/dev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 5a7e019e55..a9f1454193 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -20,7 +20,7 @@ MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains- MITX_FEATURES['SUBDOMAIN_BRANDING'] = True MITX_FEATURES['FORCE_UNIVERSITY_DOMAIN'] = None # show all university courses if in dev (ie don't use HTTP_HOST) MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True -MITX_FEATURES['ENABLE_PSYCHOMETRICS'] = True # real-time psychometrics (eg item response theory analysis in instructor dashboard) +MITX_FEATURES['ENABLE_PSYCHOMETRICS'] = False # real-time psychometrics (eg item response theory analysis in instructor dashboard) WIKI_ENABLED = True From b6b9b1591242a165c6d9365f81a012bcd4e9a705 Mon Sep 17 00:00:00 2001 From: Sarina Date: Wed, 12 Sep 2012 14:58:23 -0300 Subject: [PATCH 48/48] Make clear where to specify display_name in xml --- doc/xml-format.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/xml-format.md b/doc/xml-format.md index e1439ee97d..29c60fea99 100644 --- a/doc/xml-format.md +++ b/doc/xml-format.md @@ -299,7 +299,7 @@ This is a sketch ("tue" is not a valid start date), that should help illustrate ## Specifying metadata in the xml file -Metadata can also live in the xml files, but anything defined in the policy file overrides anything in the xml. This is primarily for backwards compatibility, and you should probably not use both. If you do leave some metadata tags in the xml, you should be consistent (e.g. if `display_name`s stay in xml, they should all stay in xml). +Metadata can also live in the xml files, but anything defined in the policy file overrides anything in the xml. This is primarily for backwards compatibility, and you should probably not use both. If you do leave some metadata tags in the xml, you should be consistent (e.g. if `display_name`s stay in xml, they should all stay in xml. Note `display_name` should be specified in the problem xml definition itself, ie, Problem Text , in file ProblemFoo.xml). - note, some xml attributes are not metadata. e.g. in `