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') #---------------------------------------- @@ -94,6 +127,8 @@ def manage_modulestores(request,reload_dir=None): html += 'commit_id=%s
' % get_commit_id(course) + for field in dumpfields: data = getattr(course,field) html += 'Grade distribution: %s
" % gsv + + # generate grade histogram + ghist = [] + + axisopts = """{ + xaxes: [{ + axisLabel: 'Grade' + }], + yaxes: [{ + position: 'left', + axisLabel: 'Count' + }] + }""" + + 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(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, align: "center" }} ], %s' % axisopts, + } + plots.append(plot) + else: + 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) + thist_json = json.dumps(sorted(thist.items(), key=lambda(x): x[0])) + + axisopts = """{ xaxes: [{ axisLabel: 'Time (min)'}], yaxes: [{position: 'left',axisLabel: 'Count'}]}""" + + plot = {'title': "Histogram of time differences between checks", + 'id': 'thistogram', + 'info': '', + 'data': "var thist = %s;\n" % thist_json, + '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): + yset = {} + gset = pmdset.filter(studentmodule__grade=grade) + ngset = gset.count() + if ngset == 0: + continue + ydat = [] + ylast = 0 + for x in xdat: + 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]) + 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['fitx'] = fitx + yset['fity'] = func_2pl(np.array(fitx), *cfp[0]) + + dataset['grade_%d' % grade] = yset + + axisopts = """{ + xaxes: [{ + axisLabel: 'Number of Attempts' + }], + yaxes: [{ + max:1.0, + position: 'left', + axisLabel: 'Probability of correctness' + }] + }""" + + # generate points for flot plot + 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']))) + 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']))) + 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) + else: + 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), + }) + + #log.debug('plots = %s' % plots) + return msg, plots + +#----------------------------------------------------------------------------- + + +def make_psychometrics_data_update_handler(studentmodule): + """ + Construct and return a procedure which may be called to update + the PsychometricsData instance for the given StudentModule instance. + """ + sm = studentmodule + try: + pmd = PsychometricData.objects.using(db).get(studentmodule=sm) + except PsychometricData.DoesNotExist: + pmd = PsychometricData(studentmodule=sm) + + def psychometrics_data_update_handler(state): + """ + This function may be called each time a problem is successfully checked + (eg on save_problem_check events in capa_module). + + state = instance state (a nice, uniform way to interface - for more future psychometric feature extraction) + """ + try: + state = json.loads(sm.state) + done = state['done'] + except: + 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 + except: + checktimes = [] + checktimes.append(datetime.datetime.now()) + pmd.checktimes = checktimes + try: + pmd.save() + except: + log.exception("Error in updating psychometrics data for %s" % sm) + + return psychometrics_data_update_handler diff --git a/lms/envs/common.py b/lms/envs/common.py index 5b488a6f8f..6c127b8049 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -71,6 +71,8 @@ MITX_FEATURES = { 'ENABLE_DISCUSSION' : False, 'ENABLE_DISCUSSION_SERVICE': True, + 'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard) + 'ENABLE_SQL_TRACKING_LOGS': False, 'ENABLE_LMS_MIGRATION': False, 'ENABLE_MANUAL_GIT_RELOAD': False, @@ -441,9 +443,9 @@ courseware_only_js += [ main_vendor_js = [ 'js/vendor/jquery.min.js', 'js/vendor/jquery-ui.min.js', - 'js/vendor/swfobject/swfobject.js', 'js/vendor/jquery.cookie.js', 'js/vendor/jquery.qtip.min.js', + 'js/vendor/swfobject/swfobject.js', ] discussion_js = sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/**/*.coffee')) @@ -619,6 +621,7 @@ INSTALLED_APPS = ( 'util', 'certificates', 'instructor', + 'psychometrics', #For the wiki 'wiki', # The new django-wiki from benjaoming diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 50befeb875..a9f1454193 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -20,6 +20,8 @@ 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'] = False # real-time psychometrics (eg item response theory analysis in instructor dashboard) + WIKI_ENABLED = True diff --git a/lms/static/images/calendar-icon.png b/lms/static/images/calendar-icon.png new file mode 100644 index 0000000000..367e4fef0a Binary files /dev/null and b/lms/static/images/calendar-icon.png differ diff --git a/lms/static/images/link-icon.png b/lms/static/images/link-icon.png new file mode 100644 index 0000000000..976197f2c8 Binary files /dev/null and b/lms/static/images/link-icon.png differ diff --git a/lms/static/images/opencourseware.png b/lms/static/images/opencourseware.png new file mode 100644 index 0000000000..b209bee857 Binary files /dev/null and b/lms/static/images/opencourseware.png differ 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 0000000000..3dc619ac7b Binary files /dev/null and b/lms/static/images/press/baltsun_logo_178x138.jpg differ diff --git a/lms/static/images/press/bostinno_logo_178x138.jpg b/lms/static/images/press/bostinno_logo_178x138.jpg new file mode 100644 index 0000000000..dadddecbf9 Binary files /dev/null and b/lms/static/images/press/bostinno_logo_178x138.jpg differ 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 0000000000..aa4f8a3577 Binary files /dev/null and b/lms/static/images/press/bostonmag_logo_178x138.jpg differ 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 0000000000..d389b7a6f1 Binary files /dev/null and b/lms/static/images/press/csmonitor_logo_178x138.jpg differ 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 0000000000..ad23f11a96 Binary files /dev/null and b/lms/static/images/press/insidehighered_logo_178x138.jpg differ diff --git a/lms/static/images/press/itbriefing_logo_178x138.jpg b/lms/static/images/press/itbriefing_logo_178x138.jpg new file mode 100755 index 0000000000..c8380ada13 Binary files /dev/null and b/lms/static/images/press/itbriefing_logo_178x138.jpg differ 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 0000000000..e29949a31f Binary files /dev/null and b/lms/static/images/press/radioboston_logo_178x138.jpg differ diff --git a/lms/static/images/press/smartplanet_logo_178x138.jpg b/lms/static/images/press/smartplanet_logo_178x138.jpg new file mode 100755 index 0000000000..534677017e Binary files /dev/null and b/lms/static/images/press/smartplanet_logo_178x138.jpg differ diff --git a/lms/static/images/press/techreview_logo_178x138.jpg b/lms/static/images/press/techreview_logo_178x138.jpg new file mode 100755 index 0000000000..ffbd3756c9 Binary files /dev/null and b/lms/static/images/press/techreview_logo_178x138.jpg differ diff --git a/lms/static/images/press/thetech_logo_178x138.jpg b/lms/static/images/press/thetech_logo_178x138.jpg new file mode 100755 index 0000000000..024fe11769 Binary files /dev/null and b/lms/static/images/press/thetech_logo_178x138.jpg differ 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 0000000000..5d884fe0fe Binary files /dev/null and b/lms/static/images/press/time_logo_178x138.jpg differ diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss index bf0565030d..508b4bfd97 100644 --- a/lms/static/sass/base/_base.scss +++ b/lms/static/sass/base/_base.scss @@ -2,6 +2,7 @@ html, body { background: rgb(250,250,250); font-family: $sans-serif; font-size: 1em; + font-style: normal; line-height: 1em; //-webkit-font-smoothing: antialiased; } @@ -42,6 +43,7 @@ p { span { font: normal 1em/1.6em $sans-serif; + color: $base-font-color; } /* Fix for CodeMirror: prevent top-level span from affecting deeply-embedded span in CodeMirror */ diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index e25bb9d8c4..20e75dbe0d 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -22,45 +22,52 @@ div.info-wrapper { @extend .clearfix; border-bottom: 1px solid lighten($border-color, 10%); list-style-type: disk; - margin-bottom: lh(); - padding-bottom: lh(.5); - - &:first-child { - margin: 0 (-(lh(.5))) lh(); - padding: lh(.5); - } + margin-bottom: lh(1.5); + padding-bottom: lh(.75); ol, ul { - margin: 0; - list-style-type: disk; - ol,ul { - list-style-type: circle; + list-style-type: disc; } } h2 { - float: left; font-size: $body-font-size; font-weight: bold; - margin: 0 flex-gutter() 0 0; - width: flex-grid(2, 9); + background: url('../images/calendar-icon.png') 0 center no-repeat; + padding-left: 20px; } section.update-description { - float: left; - margin-bottom: 0; - width: flex-grid(7, 9); + section { + &.primary { + border: 1px solid #DDD; + background: #F6F6F6; + padding: 20px; + + p { + font-weight: bold; + } + .author { + font-weight: normal; + font-style: italic; + } + } + } + + h3 { + font-size: 1em; + font-weight: bold; + margin: lh(1.5) 0 lh(.5); + } + + > ul { + list-style-type: disc; + } li { margin-bottom: lh(.5); } - - p { - &:last-child { - margin-bottom: 0; - } - } } } } diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index 0532f04b42..6f95db64ee 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; } @@ -88,7 +87,7 @@ div.course-wrapper { ul { list-style: disc outside none; padding-left: 1em; - + &.discussion-errors { list-style: none; padding-left: 2em; @@ -99,6 +98,10 @@ div.course-wrapper { } } + nav.sequence-nav ul li.prev { + left: 4px; + } + nav.sequence-bottom { ul { list-style: none; @@ -117,6 +120,7 @@ div.course-wrapper { margin: 0; @include clearfix(); padding: 0; + list-style: none; li { width: flex-grid(3, 9); diff --git a/lms/static/sass/course/courseware/_sidebar.scss b/lms/static/sass/course/courseware/_sidebar.scss index 27040bece5..4e893d2455 100644 --- a/lms/static/sass/course/courseware/_sidebar.scss +++ b/lms/static/sass/course/courseware/_sidebar.scss @@ -119,13 +119,18 @@ section.course-index { margin-bottom: 0; line-height: 1.3; - span.subtitle { + &.subtitle { color: #666; font-size: 13px; font-weight: normal; display: block; + margin: 0; + + &:empty { + display: none; + } } - } + } &:hover { background: rgba(0, 0, 0, .1); diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index 35997d95a7..1a58110563 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -2,12 +2,14 @@ @import "base/variables"; // These are all quick solutions for IE please rewrite +//Make overlay white because ie doesn't like rgba .highlighted-courses .courses .course header.course-preview, .find-courses .courses .course header.course-preview, .home .highlighted-courses > h2, .home .highlighted-courses > section.outside-app h1, section.outside-app .home .highlighted-courses > h1, header.global { background: #FFF; } +// hide all actions .home > header .title .actions, .home > header .title:hover .actions { display: none; @@ -34,10 +36,12 @@ header.global { } } +// because ie doesn't like :last .last { margin-right: 0 !important; } +// make partners not animate .home .university-partners .partners a { .name { position: static; @@ -61,7 +65,6 @@ header.global { } - .home .university-partners .partners { width: 660px; @@ -74,6 +77,7 @@ header.global { } } +// make animations on homepage not animate and show everything .highlighted-courses .courses .course, .find-courses .courses .course { .meta-info { display: none; @@ -126,10 +130,24 @@ header.global { } } +// make overlay flat black since IE cant handle rgba #lean_overlay { background: #000; } -.modal .inner-wrapper form label { - display: block; +// active navigation +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); +} + +// make dropdown user consistent size +header.global ol.user > li.primary a.dropdown { + padding-top: 6px; + padding-bottom: 6px; +} + +// always hide arrow in IE +.dashboard .my-courses .my-course .cover .arrow { + display: none; } diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss index 3f2813ba0e..d23917fe27 100644 --- a/lms/static/sass/multicourse/_course_about.scss +++ b/lms/static/sass/multicourse/_course_about.scss @@ -335,13 +335,43 @@ .course-sidebar { @include box-sizing(border-box); - @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15)); - border: 1px solid rgb(200,200,200); - border-top: none; float: left; - padding: 16px 20px 30px; width: flex-grid(4); + > section { + @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15)); + border: 1px solid rgb(200,200,200); + + &.course-summary { + padding: 16px 20px 30px; + margin-bottom: 220px; + border-top: none; + } + + &.additional-resources { + padding: 30px; + + .opencourseware { + text-indent: -9999px; + background: url('../images/opencourseware.png') 0 0 no-repeat; + width: 266px; + height: 31px; + margin-bottom: 20px; + } + + ul { + padding-left: 0; + margin-bottom: 0; + } + + li { + list-style: none; + padding-left: 29px; + background: url('../images/link-icon.png') left center no-repeat; + } + } + } + header { margin-bottom: 30px; padding-bottom: 16px; @@ -441,6 +471,13 @@ } } } + + h1 { + font: 1em $serif; + letter-spacing: 0; + color: #999; + margin-bottom: 0; + } } .important-dates { 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); diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index 409c6088ec..1685487b89 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -147,7 +147,7 @@ } label { - display: none; + color: #999; &.field-error { display: block; diff --git a/lms/templates/accordion.html b/lms/templates/accordion.html index fa91927462..ef2dabeeff 100644 --- a/lms/templates/accordion.html +++ b/lms/templates/accordion.html @@ -9,11 +9,8 @@ % for section in chapter['sections']:${section['display_name']} - - ${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''} - -
+${section['display_name']}
+${section['format']} ${"due " + section['due'] if 'due' in section and section['due'] != '' else ''}
@@ -99,14 +159,45 @@ table.stat_table td { %endfor
+%endif + +##----------------------------------------------------------------------------- +%if modeflag.get('Psychometrics'): + + %for plot in plots: +${plot['info']}
+${msg}
%endif -% if course_errors is not UNDEFINED: +##----------------------------------------------------------------------------- +%if modeflag.get('Admin'): + % if course_errors is not UNDEFINED:Welcome ${extauth_email}
Enter a public username:
- + % endif