From d68945de334912ba24cfbd93948f0ecd572d2e60 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Tue, 30 Oct 2012 15:04:21 -0400 Subject: [PATCH 001/243] started adding settings page --- cms/djangoapps/contentstore/views.py | 4 +++ cms/static/sass/_settings.scss | 39 ++++++++++++++++++++++ cms/static/sass/base-style.scss | 1 + cms/templates/settings.html | 48 ++++++++++++++++++++++++++++ cms/urls.py | 1 + 5 files changed, 93 insertions(+) create mode 100644 cms/static/sass/_settings.scss create mode 100644 cms/templates/settings.html diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 05cde6043d..8794404118 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -869,6 +869,10 @@ def edit_static(request, org, course, coursename): return render_to_response('edit-static-page.html', {}) +def settings(request, org, course, coursename): + return render_to_response('settings.html', {}) + + def not_found(request): return render_to_response('error.html', {'error': '404'}) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss new file mode 100644 index 0000000000..7dd349adb9 --- /dev/null +++ b/cms/static/sass/_settings.scss @@ -0,0 +1,39 @@ +.settings { + .settings-overview { + @extend .window; + padding: 30px 40px; + + .details { + margin-bottom: 20px; + font-size: 14px; + } + + .row { + margin-bottom: 20px; + padding-bottom: 0; + border-bottom: none; + } + + label { + display: inline-block; + width: 200px; + font-size: 14px; + + &.check-label { + display: inline; + } + } + } + + h2 { + margin-bottom: 20px; + font-size: 24px; + font-weight: 300; + } + + section { + border-bottom: 1px solid $mediumGrey; + margin-bottom: 40px; + padding-bottom: 40px; + } +} \ No newline at end of file diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index 6a6105c109..73812125a8 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -18,6 +18,7 @@ @import "static-pages"; @import "users"; @import "import"; +@import "settings"; @import "course-info"; @import "landing"; @import "graphics"; diff --git a/cms/templates/settings.html b/cms/templates/settings.html new file mode 100644 index 0000000000..8df3cede31 --- /dev/null +++ b/cms/templates/settings.html @@ -0,0 +1,48 @@ +<%inherit file="base.html" /> +<%! from django.core.urlresolvers import reverse %> +<%block name="bodyclass">settings +<%block name="title">Settings + +<%namespace name='static' file='static_content.html'/> + +<%block name="jsextra"> + + + +<%block name="content"> +
+
+

Settings

+
+
+

Details

+
+ + +
+
+ + +
+
+ + +
+
+
+

Grading

+
+ + +
+
+
+

Problems

+
+ +
+
+
+
+
+ diff --git a/cms/urls.py b/cms/urls.py index 7b3dd90a0b..620460ad35 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -37,6 +37,7 @@ urlpatterns = ('', url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', name='static_pages'), url(r'^edit_static/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_static', name='edit_static'), url(r'^(?P[^/]+)/(?P[^/]+)/assets/(?P[^/]+)$', 'contentstore.views.asset_index', name='asset_index'), + url(r'^settings/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.settings', name='settings'), # temporary landing page for a course url(r'^edge/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.landing', name='landing'), From ef4b2f044f7a0257d505d153d19437949123e7c2 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Wed, 31 Oct 2012 17:03:06 -0400 Subject: [PATCH 002/243] =?UTF-8?q?basic=20settings=20framework=20?= =?UTF-8?q?=E2=80=93=20wip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cms/static/sass/_settings.scss | 244 ++++++++++++++++++++++++++++++--- cms/templates/settings.html | 197 ++++++++++++++++++++++---- 2 files changed, 396 insertions(+), 45 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 7dd349adb9..97d391525b 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -1,39 +1,251 @@ .settings { .settings-overview { @extend .window; - padding: 30px 40px; + @include clearfix; + display: table; + width: 100%; - .details { - margin-bottom: 20px; - font-size: 14px; + .sidebar { + display: table-cell; + float: none; + width: 20%; + padding: 30px 0 30px 20px; + border-radius: 3px 0 0 3px; + background: $lightGrey; } - .row { - margin-bottom: 20px; - padding-bottom: 0; - border-bottom: none; + .main-column { + display: table-cell; + float: none; + width: 80%; + padding: 30px 40px 30px 60px; } label { display: inline-block; width: 200px; - font-size: 14px; + font-size: 15px; + font-weight: 400; &.check-label { display: inline; + margin-left: 10px; + } + + &.cutoff { + float: left; + width: 90px; + line-height: 38px; + } + } + + input { + font-size: 15px; + + &.long { + width: 400px; + } + + &.short { + width: 100px; + } + + &.date { + width: 140px; + } + } + + .settings-page-section { + > section { + display: none; + margin-bottom: 40px; + + &.is-shown { + display: block; + } + + .row { + margin-bottom: 20px; + padding: 0; + border-bottom: none; + + &:last-child { + margin-bottom: 0; + } + } + + &:last-child { + border-bottom: none; + } + + > section { + padding-bottom: 40px; + margin-bottom: 40px; + border-radius: 3px; + border-bottom: 1px solid $mediumGrey; + @include clearfix; + + &:last-child { + padding-bottom: 0; + border-bottom: none; + } + } + } + } + + .settings-page-menu { + a { + display: block; + padding-left: 20px; + line-height: 52px; + + &.is-shown { + background: #fff; + border-radius: 5px 0 0 5px; + } } } } h2 { - margin-bottom: 20px; - font-size: 24px; - font-weight: 300; + margin-bottom: 30px; + font-size: 28px; + font-weight: 300; + color: $blue; } - section { - border-bottom: 1px solid $mediumGrey; - margin-bottom: 40px; - padding-bottom: 40px; + h3 { + margin-bottom: 20px; + font-size: 15px; + font-weight: 700; + line-height: 34px; + color: $blue; + } + + .grade-slider { + float: left; + width: 500px; + + .grade-bar { + position: relative; + width: 100%; + height: 40px; + background: $lightGrey; + + .increments { + position: relative; + + li { + position: absolute; + top: 42px; + width: 30px; + margin-left: -15px; + font-size: 9px; + text-align: center; + + &.increment-0 { + left: 0; + } + + &.increment-10 { + left: 10%; + } + + &.increment-20 { + left: 20%; + } + + &.increment-30 { + left: 30%; + } + + &.increment-40 { + left: 40%; + } + + &.increment-50 { + left: 50%; + } + + &.increment-60 { + left: 60%; + } + + &.increment-70 { + left: 70%; + } + + &.increment-80 { + left: 80%; + } + + &.increment-90 { + left: 90%; + } + + &.increment-100 { + left: 100%; + } + } + } + + .grades { + position: relative; + + li { + position: absolute; + top: 0; + height: 40px; + text-align: right; + color: rgba(0, 0, 0, .5); + + &.bar-a { + background: #4fe696; + width: 100%; + } + + &.bar-b { + background: #ffdf7e; + width: 80%; + } + + &.bar-c { + background: #ef68a6; + width: 70%; + } + + .letter-grade { + display: block; + margin: 6px 5px 0 0; + font-size: 14px; + font-weight: 700; + line-height: 14px; + } + + .range { + display: block; + margin-right: 5px; + font-size: 10px; + line-height: 12px; + } + + .drag-bar { + position: absolute; + top: 0; + right: -1px; + height: 40px; + width: 2px; + background-color: #fff; + cursor: ew-resize; + @include transition(none); + + &:hover { + width: 4px; + right: -2px; + } + } + } + } + } } } \ No newline at end of file diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 8df3cede31..0d8f7f05b0 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -6,42 +6,181 @@ <%namespace name='static' file='static_content.html'/> <%block name="jsextra"> + <%block name="content">
-
+

Settings

-
-

Details

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

Grading

-
- - -
-
-
-

Problems

-
- -
-
+ +
+
+

Course Details

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+

Grading

+
+ +
+
+
    +
  1. 0
  2. +
  3. 10
  4. +
  5. 20
  6. +
  7. 30
  8. +
  9. 40
  10. +
  11. 50
  12. +
  13. 60
  14. +
  15. 70
  16. +
  17. 80
  18. +
  19. 90
  20. +
  21. 100
  22. +
+
    +
  1. + A + 80–100 +
  2. +
  3. + B + 70–80 + +
  4. +
  5. + C + 0–70 + +
  6. +
+
+
+
+
+
+ + +
+
+
+

Homework

+
+ + +
+
+ + +
+
+ + +
+
+
+

Lab

+
+ + +
+
+ + +
+
+ + +
+
+
+
+

Problems

+
+ +
+
+
From a6d5edce5e871261d0ee4c5ed91089c8e1a0c10a Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Thu, 1 Nov 2012 11:34:02 -0400 Subject: [PATCH 003/243] polished grade range slider --- cms/static/sass/_settings.scss | 79 +++++++++++++++++++------ cms/templates/settings.html | 105 +++++++++++++++++++++------------ 2 files changed, 128 insertions(+), 56 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 97d391525b..0834a90893 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -32,10 +32,8 @@ margin-left: 10px; } - &.cutoff { - float: left; - width: 90px; - line-height: 38px; + &.ranges { + margin-bottom: 20px; } } @@ -115,16 +113,43 @@ } h3 { - margin-bottom: 20px; + margin-bottom: 30px; font-size: 15px; font-weight: 700; - line-height: 34px; color: $blue; } + .grade-controls { + @include clearfix; + } + + .new-grade-button { + position: relative; + float: left; + display: block; + width: 29px; + height: 29px; + margin: 4px 10px 0 0; + border-radius: 20px; + border: 1px solid $darkGrey; + @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); + background-color: #d1dae3; + @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); + color: #6d788b; + + .plus-icon { + position: absolute; + top: 50%; + left: 50%; + margin-left: -6px; + margin-top: -6px; + } + } + .grade-slider { float: left; - width: 500px; + width: 560px; + height: 60px; .grade-bar { position: relative; @@ -197,26 +222,46 @@ top: 0; height: 40px; text-align: right; - color: rgba(0, 0, 0, .5); - &.bar-a { + &:hover, + &.is-dragging { + .remove-button { + display: block; + } + } + + .remove-button { + display: none; + position: absolute; + top: -17px; + right: 1px; + height: 17px; + font-size: 10px; + } + + &:nth-child(1) { background: #4fe696; - width: 100%; } - &.bar-b { + &:nth-child(2) { background: #ffdf7e; - width: 80%; } - &.bar-c { - background: #ef68a6; - width: 70%; + &:nth-child(3) { + background: #ffb657; + } + + &:nth-child(4) { + background: #fb336c; + } + + &:nth-child(5) { + background: #ef54a1; } .letter-grade { display: block; - margin: 6px 5px 0 0; + margin: 7px 5px 0 0; font-size: 14px; font-weight: 700; line-height: 14px; @@ -225,7 +270,7 @@ .range { display: block; margin-right: 5px; - font-size: 10px; + font-size: 9px; line-height: 12px; } diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 0d8f7f05b0..1630ad4ffa 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -13,15 +13,31 @@ var barOrigin; var barWidth; var gradeThresholds; + var GRADES = ['A', 'B', 'C', 'D', 'E']; (function() { $body = $('body'); $gradeBar = $('.grade-bar'); gradeThresholds = [100, 80, 70]; $('.settings-page-menu a').bind('click', showSettingsTab); - $('.drag-bar').bind('mousedown', startDragBar); + $body.on('mousedown', '.drag-bar', startDragBar); + $('.new-grade-button').bind('click', addNewGrade); + $body.on('click', '.remove-button', removeGrade); })(); + function addNewGrade(e) { + e.preventDefault(); + var $newGradeBar = $('
  • ' + GRADES[$('.grades li').length] + 'remove
  • '); + $('.grades').append($newGradeBar); + } + + function removeGrade(e) { + e.preventDefault(); + var index = $(this).closest('li').index(); + gradeThresholds.splice(index, 1); + $(this).closest('li').remove(); + } + function showSettingsTab(e) { e.preventDefault(); $('.settings-page-section > section').hide(); @@ -34,28 +50,33 @@ e.preventDefault(); barOrigin = $gradeBar.offset().left; barWidth = $gradeBar.width(); - $draggingBar = $(e.target).closest('li'); + $draggingBar = $(e.target).closest('li').addClass('is-dragging'); $body.bind('mousemove', moveBar); $body.bind('mouseup', stopDragBar); } function moveBar(e) { - var percentage = (e.pageX - barOrigin) / barWidth * 100; + var barIndex = $draggingBar.index(); + console.log(barIndex); + var min = gradeThresholds[barIndex + 1] || 0; + var max = gradeThresholds[barIndex - 1] || 100; + var percentage = Math.min(Math.max((e.pageX - barOrigin) / barWidth * 100, min), max); $draggingBar.css('width', percentage + '%'); gradeThresholds[$draggingBar.index()] = Math.round(percentage); renderGradeRanges(); } function stopDragBar(e) { + $draggingBar.removeClass('is-dragging'); $body.unbind('mousemove', moveBar); $body.unbind('mouseup', stopDragBar); } function renderGradeRanges() { $('.range').each(function(i) { - var min = gradeThresholds[i + 1] || 0; + var min = gradeThresholds[i + 1] + 1 || 0; var max = gradeThresholds[i]; - $(this).text(min + '–' + max); + $(this).text(min + '-' + max); }); } @@ -76,7 +97,7 @@
    -
    +

    Course Details

    @@ -99,41 +120,47 @@
    -
    +

    Grading

    - -
    -
    -
      -
    1. 0
    2. -
    3. 10
    4. -
    5. 20
    6. -
    7. 30
    8. -
    9. 40
    10. -
    11. 50
    12. -
    13. 60
    14. -
    15. 70
    16. -
    17. 80
    18. -
    19. 90
    20. -
    21. 100
    22. -
    -
      -
    1. - A - 80–100 -
    2. -
    3. - B - 70–80 - -
    4. -
    5. - C - 0–70 - -
    6. -
    + +
    + +
    +
    +
      +
    1. 0
    2. +
    3. 10
    4. +
    5. 20
    6. +
    7. 30
    8. +
    9. 40
    10. +
    11. 50
    12. +
    13. 60
    14. +
    15. 70
    16. +
    17. 80
    18. +
    19. 90
    20. +
    21. 100
    22. +
    +
      +
    1. + A + 81-100 + remove +
    2. +
    3. + B + 71-80 + + remove +
    4. +
    5. + C + 0-70 + + remove +
    6. +
    +
    From 7887f92e3ba50517eb388e94e51257c395986b52 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Thu, 1 Nov 2012 11:34:31 -0400 Subject: [PATCH 004/243] removed log --- cms/templates/settings.html | 1 - 1 file changed, 1 deletion(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 1630ad4ffa..4110f72ef4 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -57,7 +57,6 @@ function moveBar(e) { var barIndex = $draggingBar.index(); - console.log(barIndex); var min = gradeThresholds[barIndex + 1] || 0; var max = gradeThresholds[barIndex - 1] || 100; var percentage = Math.min(Math.max((e.pageX - barOrigin) / barWidth * 100, min), max); From 3b42ea7e9e037e2f21e2ab2443fb580d1a0bee57 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 7 Nov 2012 15:00:48 -0500 Subject: [PATCH 005/243] added basic settings markup and revised sections based on additional fields/info needed --- cms/static/sass/_base.scss | 3 +- cms/static/sass/_settings.scss | 14 +- cms/templates/settings.html | 227 ++++++++++++++++++++++++++++++--- 3 files changed, 222 insertions(+), 22 deletions(-) diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index c1875edb06..a91d703d0f 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -80,7 +80,8 @@ footer { input[type="text"], input[type="email"], -input[type="password"] { +input[type="password"], +textarea { padding: 6px 8px 8px; @include box-sizing(border-box); border: 1px solid #b0b6c2; diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 0834a90893..e26bf8dd8d 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -37,13 +37,17 @@ } } - input { + input, textarea { font-size: 15px; &.long { width: 400px; } + &.tall { + height: 200px; + } + &.short { width: 100px; } @@ -54,6 +58,14 @@ } .settings-page-section { + > .alert { + display: none; + + &.is-shown { + display: block; + } + } + > section { display: none; margin-bottom: 40px; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 4110f72ef4..fa5443a0c0 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -90,35 +90,211 @@
    +

    Course Details

    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    + +
    +
    +

    Basic Information

    + The nuts and bolts of your class +
    + +
    + + +
    +
    + + +
    +
    + + + e.g. 101x +
    +
    + + +
    +
    + +
    +
    +

    Dates & Times

    + The nuts and bolts of your class +
    + +
    + + + First day the class begins +
    + +
    + + + Last day of class activty +
    + +
    + + +
    +
      +
    1. +
      + + Milestone Date +
      + +
      + + Milestone Name +
      + + +
    2. +
    + + + New Class Milestone + +
    +
    +
    + +
    +
    +

    Introducing Your Course

    + How your course will be shown to students considering taking it +
    + +
    + + +
    + +
    + + + Used to introduce your class to perspective students +
    + +
    + + +
    +
    + +
    +
    +

    Requirements

    + Expectations of the students taking this course +
    + +
    + + +
    + +
    + + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
      +
    1. +
      + + Textbook Name +
      + +
      + + Textbook URL +
      + + +
    2. +
    + + + New Textbook + +
    +
    +
    + +
    +
    +

    More Information

    + Other helpful information about the course +
    + +
    + + +
    +
      +
    1. +
      + + Question +
      + +
      + + Answer +
      + + +
    2. +
    + + + New Question & Answer + +
    +
    +
    +
    + +
    +

    Course Staff

    + +
    +

    Grading

    @@ -200,6 +376,17 @@
    + +
    +

    Handouts & Guides

    + +
    + + + PDF formatted file +
    +
    +

    Problems

    From 8f2ab52215a5d9e113b66669c28907d1b28fa66a Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 8 Nov 2012 17:48:38 -0500 Subject: [PATCH 006/243] in progress form styling of course info --- cms/static/sass/_settings.scss | 95 +++++++++++++++++++++++++++++++++- cms/templates/settings.html | 20 ++++--- 2 files changed, 102 insertions(+), 13 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index e26bf8dd8d..a3d74a5bd0 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -23,6 +23,7 @@ label { display: inline-block; + vertical-align: top; width: 200px; font-size: 15px; font-weight: 400; @@ -37,6 +38,12 @@ } } + .label-micro { + display: block; + margin-top: 5px; + font-size: 13px; + } + input, textarea { font-size: 15px; @@ -55,6 +62,61 @@ &.date { width: 140px; } + + &:focus { + border-color: $blue; + outline: 0; + } + } + + .field { + background: #b6eab1; + display: inline-block; + vertical-align: top; + min-width: 400px; + + input { + display: block; + } + + .input-list { + + .element { + position: relative; + width: 100%; + @include clearfix(); + + div { + float: left; + margin-right: 20px; + + &:last-child { + margin-right: 0; + } + } + + .delete-icon { + position: absolute; + right: 10px; + top: 10px; + } + } + + &.element-stacked { + + } + + &.element-multi { + + } + } + + .new-item { + margin-top: 20px; + padding-bottom: 10px; + @include grey-button; + @include box-sizing(border-box); + } } .settings-page-section { @@ -82,6 +144,13 @@ &:last-child { margin-bottom: 0; } + + .tip { + color: $mediumGrey; + display: inline-block; + margin-left: 10px; + font-size: 13px; + } } &:last-child { @@ -89,12 +158,34 @@ } > section { - padding-bottom: 40px; + padding-bottom: 60px; margin-bottom: 40px; border-radius: 3px; - border-bottom: 1px solid $mediumGrey; + border-bottom: 1px solid $lightGrey; @include clearfix; + header { + @include clearfix; + border-bottom: 1px solid $mediumGrey; + margin-bottom: 20px; + padding-bottom: 10px; + + h3 { + color: $darkGrey; + float: left; + + margin: 0 40px 0 0; + text-transform: uppercase; + } + + .detail { + float: right; + marign-top: 3px; + color: $mediumGrey; + font-size: 13px; + } + } + &:last-child { padding-bottom: 0; border-bottom: none; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index fa5443a0c0..e13741ec4c 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -150,23 +150,22 @@
      -
    1. +
    2. Milestone Date
      - + Milestone Name
      -
    - New Class Milestone + New Class Milestone
    @@ -213,7 +212,7 @@ - New Link + New Link @@ -233,7 +232,7 @@
      -
    1. +
    2. Textbook Name @@ -244,12 +243,12 @@ Textbook URL
      - +
    - New Textbook + New Textbook
    @@ -266,7 +265,7 @@
      -
    1. +
    2. Question @@ -277,12 +276,11 @@ Answer
      -
    - New Question & Answer + New Question & Answer
    From f1e1f9e7e5b6f943b86036da31773faa610e0d3d Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 8 Nov 2012 18:11:48 -0500 Subject: [PATCH 007/243] in progress field layout styling --- cms/static/sass/_settings.scss | 51 ++++++++++++++++++++++------------ cms/templates/settings.html | 18 ++++++------ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index a3d74a5bd0..4d292c8f31 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -64,16 +64,16 @@ } &:focus { + @include linear-gradient(tint($blue, 80%), tint($blue, 90%)); border-color: $blue; outline: 0; } } .field { - background: #b6eab1; display: inline-block; vertical-align: top; - min-width: 400px; + max-width: 400px; input { display: block; @@ -87,12 +87,7 @@ @include clearfix(); div { - float: left; - margin-right: 20px; - &:last-child { - margin-right: 0; - } } .delete-icon { @@ -102,12 +97,27 @@ } } - &.element-stacked { + .element-stacked { + div { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + } } - &.element-multi { + .element-multi { + div { + float: left; + margin-right: 20px; + + &:last-child { + margin-right: 0; + } + } } } @@ -137,19 +147,29 @@ } .row { - margin-bottom: 20px; - padding: 0; - border-bottom: none; + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px solid $lightGrey; &:last-child { margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; } .tip { color: $mediumGrey; + font-size: 13px; + } + + .tip-inline { display: inline-block; margin-left: 10px; - font-size: 13px; + } + + .tip-stacked { + display: block; + margin: 10px 0 0 200px; } } @@ -158,10 +178,7 @@ } > section { - padding-bottom: 60px; - margin-bottom: 40px; - border-radius: 3px; - border-bottom: 1px solid $lightGrey; + margin-bottom: 100px; @include clearfix; header { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index e13741ec4c..4586fc423c 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -92,7 +92,7 @@
  • Course Details
  • Staff
  • Grading
  • -
  • Handouts & Guides
  • +
  • Handouts
  • Problems
  • @@ -119,7 +119,7 @@
    - e.g. 101x + e.g. 101x
    @@ -136,13 +136,13 @@
    - First day the class begins + First day the class begins
    - Last day of class activty + Last day of class activty
    @@ -174,7 +174,7 @@

    Introducing Your Course

    - How your course will be shown to students considering taking it + Information for perspective students
    @@ -185,7 +185,7 @@
    - Used to introduce your class to perspective students + Used to introduce your class to perspective students
    @@ -243,7 +243,7 @@ Textbook URL
    - + @@ -261,11 +261,11 @@
    - +
      -
    1. +
    2. Question From b1451127408c6bab206ef23e14bb3573524cf122 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 9 Nov 2012 08:39:54 -0500 Subject: [PATCH 008/243] multi-input field markup and styling --- cms/static/sass/_settings.scss | 27 +++++++++++++++++++++++---- cms/templates/settings.html | 10 ++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 4d292c8f31..b0f45b2197 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -90,10 +90,24 @@ } - .delete-icon { - position: absolute; - right: 10px; - top: 10px; + .remove-item { + display: block; + border-top: 1px solid $lightGrey; + margin-top: 5px; + padding-top: 10px; + font-size: 13px; + } + } + + .element-group { + width: 400px; + padding: 15px 20px; + background: tint($lightGrey, 50%); + @include border-radius(3px); + @include box-sizing(border-box); + + input.long, textarea { + width: 100%; } } @@ -118,6 +132,11 @@ margin-right: 0; } } + + .remove-item { + float: left; + width: 100%; + } } } diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 4586fc423c..fe3c59f10f 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -150,7 +150,7 @@
        -
      1. +
      2. Milestone Date @@ -161,6 +161,7 @@ Milestone Name
        + Delete Milestone
      @@ -232,7 +233,7 @@
        -
      1. +
      2. Textbook Name @@ -243,7 +244,7 @@ Textbook URL
        - + Delete Textbook
      @@ -265,7 +266,7 @@
        -
      1. +
      2. Question @@ -276,6 +277,7 @@ Answer
        + Delete Question & Answer
      From 39fda1ea9bb04b60daa6fc1e31508a1268d925ea Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 9 Nov 2012 10:31:08 -0500 Subject: [PATCH 009/243] in progress staff section styling --- cms/static/sass/_settings.scss | 37 ++++++++++++++++++++++-- cms/templates/settings.html | 51 ++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index b0f45b2197..c2e285ce4a 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -70,6 +70,15 @@ } } + ::-webkit-input-placeholder { + color: $mediumGrey; + font-size: 13px; + } + :-moz-placeholder { + color: $mediumGrey; + font-size: 13px; + } + .field { display: inline-block; vertical-align: top; @@ -93,7 +102,7 @@ .remove-item { display: block; border-top: 1px solid $lightGrey; - margin-top: 5px; + margin-top: 10px; padding-top: 10px; font-size: 13px; } @@ -101,7 +110,7 @@ .element-group { width: 400px; - padding: 15px 20px; + padding: 15px; background: tint($lightGrey, 50%); @include border-radius(3px); @include box-sizing(border-box); @@ -242,6 +251,30 @@ } } } + + .settings-details { + + } + + .settings-staff { + + } + + .settings-grading { + + } + + .settings-handouts { + + } + + .settings-problems { + + } + + .settings-discussions { + + } } h2 { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index fe3c59f10f..77800f99c5 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -94,6 +94,7 @@
    3. Grading
    4. Handouts
    5. Problems
    6. +
    7. Discussions
    @@ -152,7 +153,7 @@
    1. - + Milestone Date
      @@ -210,7 +211,16 @@
    + +
    +

    Discussions

    + +
    From 65869461f819e3bcd3dad5908e40cd3d6b522056 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 14 Nov 2012 14:50:48 -0500 Subject: [PATCH 010/243] finalized layout and styling for course details and faculty settings --- cms/static/sass/_settings.scss | 765 +++++++++++++++++++-------------- cms/templates/settings.html | 619 +++++++++++++++----------- 2 files changed, 809 insertions(+), 575 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index c2e285ce4a..40d991937d 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -5,12 +5,13 @@ display: table; width: 100%; + // layout .sidebar { display: table-cell; float: none; width: 20%; padding: 30px 0 30px 20px; - border-radius: 3px 0 0 3px; + @include border-radius(3px 0 0 3px); background: $lightGrey; } @@ -21,139 +22,16 @@ padding: 30px 40px 30px 60px; } - label { - display: inline-block; - vertical-align: top; - width: 200px; - font-size: 15px; - font-weight: 400; - - &.check-label { - display: inline; - margin-left: 10px; - } - - &.ranges { - margin-bottom: 20px; - } - } - - .label-micro { - display: block; - margin-top: 5px; - font-size: 13px; - } - - input, textarea { - font-size: 15px; - - &.long { - width: 400px; - } - - &.tall { - height: 200px; - } - - &.short { - width: 100px; - } - - &.date { - width: 140px; - } - - &:focus { - @include linear-gradient(tint($blue, 80%), tint($blue, 90%)); - border-color: $blue; - outline: 0; - } - } - - ::-webkit-input-placeholder { - color: $mediumGrey; - font-size: 13px; - } - :-moz-placeholder { - color: $mediumGrey; - font-size: 13px; - } - - .field { - display: inline-block; - vertical-align: top; - max-width: 400px; - - input { + .settings-page-menu { + a { display: block; - } + padding-left: 20px; + line-height: 52px; - .input-list { - - .element { - position: relative; - width: 100%; - @include clearfix(); - - div { - - } - - .remove-item { - display: block; - border-top: 1px solid $lightGrey; - margin-top: 10px; - padding-top: 10px; - font-size: 13px; - } + &.is-shown { + background: #fff; + @include border-radius(5px 0 0 5px); } - - .element-group { - width: 400px; - padding: 15px; - background: tint($lightGrey, 50%); - @include border-radius(3px); - @include box-sizing(border-box); - - input.long, textarea { - width: 100%; - } - } - - .element-stacked { - - div { - margin-bottom: 20px; - - &:last-child { - margin-bottom: 0; - } - } - } - - .element-multi { - - div { - float: left; - margin-right: 20px; - - &:last-child { - margin-right: 0; - } - } - - .remove-item { - float: left; - width: 100%; - } - } - } - - .new-item { - margin-top: 20px; - padding-bottom: 10px; - @include grey-button; - @include box-sizing(border-box); } } @@ -174,37 +52,17 @@ display: block; } - .row { - margin-bottom: 15px; - padding-bottom: 15px; - border-bottom: 1px solid $lightGrey; - - &:last-child { - margin-bottom: 0; - padding-bottom: 0; - border-bottom: none; - } - - .tip { - color: $mediumGrey; - font-size: 13px; - } - - .tip-inline { - display: inline-block; - margin-left: 10px; - } - - .tip-stacked { - display: block; - margin: 10px 0 0 200px; - } - } - &:last-child { border-bottom: none; } + > .title { + margin-bottom: 30px; + font-size: 28px; + font-weight: 300; + color: $blue; + } + > section { margin-bottom: 100px; @include clearfix; @@ -239,25 +97,276 @@ } } - .settings-page-menu { - a { - display: block; - padding-left: 20px; - line-height: 52px; + // form basics + label, .label { + padding: 0; + border: none; + background: none; + font-size: 15px; + font-weight: 400; - &.is-shown { - background: #fff; - border-radius: 5px 0 0 5px; + &.check-label { + display: inline; + margin-left: 10px; + } + + &.ranges { + margin-bottom: 20px; + } + } + + input, textarea { + @include transition(all 1s ease-in-out); + @include box-sizing(border-box); + font-size: 15px; + + &.long { + width: 100%; + } + + &.tall { + height: 200px; + } + + &.short { + width: 25%; + } + + &.date { + + } + + &:focus { + @include linear-gradient(tint($blue, 80%), tint($blue, 90%)); + border-color: $blue; + outline: 0; + } + } + + ::-webkit-input-placeholder { + color: $mediumGrey; + font-size: 13px; + } + :-moz-placeholder { + color: $mediumGrey; + font-size: 13px; + } + + .tip { + color: $mediumGrey; + font-size: 13px; + } + + + // form layouts + .row { + margin-bottom: 30px; + padding-bottom: 30px; + border-bottom: 1px solid $lightGrey; + + &:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; + } + + // structural labels, not semantic labels per se + > label, .label { + display: inline-block; + vertical-align: top; + width: 200px; + } + + // tips + .tip-inline { + display: inline-block; + margin-left: 10px; + } + + .tip-stacked { + display: block; + margin-top: 10px; + } + + // structural field, not semantic fields per se + .field { + display: inline-block; + width: 400px; + + > input, > textarea, .input { + display: inline-block; + + &:last-child { + margin-bottom: 0; + } + + .group { + input, textarea { + margin-bottom: 5px; + } + + .label, label { + font-size: 13px; + } + } + + // multi-field + &.multi { + display: block; + background: tint($lightGrey, 50%); + padding: 15px; + @include border-radius(4px); + @include box-sizing(border-box); + + .group { + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + + input, .input, textarea { + + } + } + } + + // multi stacked + &.multi-stacked { + + .group { + input, .input, textarea { + width: 100%; + } + } + } + + // multi-field inline + &.multi-inline { + @include clearfix; + + .group { + float: left; + margin-right: 20px; + width: 170px; + + &:nth-child(2) { + margin-right: 0; + } + + .input, input, textarea { + width: 100%; + } + } + + .remove-item { + float: right; + } + } + } + + // input-list + .input-list { + + .input { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px dotted $lightGrey; + + &:last-child { + border: 0; + } + } + } + + // enumerated inputs + &.enum { } } } + // editing controls - adding + .new-item, .replace-item { + clear: both; + display: block; + margin-top: 10px; + padding-bottom: 10px; + @include grey-button; + @include box-sizing(border-box); + } + + + // editing controls - removing + .remove-item { + clear: both; + display: block; + opacity: 0.75; + font-size: 13px; + text-align: right; + @include transition(opacity 0.25s ease-in-out); + + + &:hover { + color: $blue; + opacity: 0.99; + } + } + + // editing controls - preview + .input-existing { + display: block !important; + + .current { + width: 100%; + margin: 10px 0; + padding: 15px; + @include box-sizing(border-box); + @include border-radius(5px); + background: tint($blue, 80%); + } + } + + + // specific sections .settings-details { } - .settings-staff { + .settings-faculty { + .settings-faculty-members { + + > header { + display: none; + } + + .field .multi { + display: block; + margin-bottom: 40px; + padding: 20px; + background: tint($lightGrey, 50%); + @include border-radius(4px); + @include box-sizing(border-box); + } + + .course-faculty-list-item { + + .row { + + &:nth-child(4) { + padding-bottom: 0; + border-bottom: none; + } + } + } + + #course-faculty-bio-input { + margin-bottom: 0; + } + + .new-course-faculty-item { + } + } } .settings-grading { @@ -275,194 +384,200 @@ .settings-discussions { } - } - h2 { - margin-bottom: 30px; - font-size: 28px; - font-weight: 300; - color: $blue; - } + // states + label.is-focused { + color: $blue; + @include transition(color 1s ease-in-out); + } - h3 { - margin-bottom: 30px; - font-size: 15px; - font-weight: 700; - color: $blue; - } - - .grade-controls { - @include clearfix; - } - - .new-grade-button { - position: relative; - float: left; - display: block; - width: 29px; - height: 29px; - margin: 4px 10px 0 0; - border-radius: 20px; - border: 1px solid $darkGrey; - @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); - background-color: #d1dae3; - @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); - color: #6d788b; - - .plus-icon { - position: absolute; - top: 50%; - left: 50%; - margin-left: -6px; - margin-top: -6px; + // misc + .divide { + display: none; } } - .grade-slider { - float: left; - width: 560px; - height: 60px; - .grade-bar { - position: relative; - width: 100%; - height: 40px; - background: $lightGrey; - .increments { - position: relative; + // h3 { + // margin-bottom: 30px; + // font-size: 15px; + // font-weight: 700; + // color: $blue; + // } - li { - position: absolute; - top: 42px; - width: 30px; - margin-left: -15px; - font-size: 9px; - text-align: center; + // .grade-controls { + // @include clearfix; + // } - &.increment-0 { - left: 0; - } + // .new-grade-button { + // position: relative; + // float: left; + // display: block; + // width: 29px; + // height: 29px; + // margin: 4px 10px 0 0; + // border-radius: 20px; + // border: 1px solid $darkGrey; + // @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); + // background-color: #d1dae3; + // @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); + // color: #6d788b; - &.increment-10 { - left: 10%; - } + // .plus-icon { + // position: absolute; + // top: 50%; + // left: 50%; + // margin-left: -6px; + // margin-top: -6px; + // } + // } - &.increment-20 { - left: 20%; - } + // .grade-slider { + // float: left; + // width: 560px; + // height: 60px; - &.increment-30 { - left: 30%; - } + // .grade-bar { + // position: relative; + // width: 100%; + // height: 40px; + // background: $lightGrey; - &.increment-40 { - left: 40%; - } + // .increments { + // position: relative; - &.increment-50 { - left: 50%; - } + // li { + // position: absolute; + // top: 42px; + // width: 30px; + // margin-left: -15px; + // font-size: 9px; + // text-align: center; - &.increment-60 { - left: 60%; - } + // &.increment-0 { + // left: 0; + // } - &.increment-70 { - left: 70%; - } + // &.increment-10 { + // left: 10%; + // } - &.increment-80 { - left: 80%; - } + // &.increment-20 { + // left: 20%; + // } - &.increment-90 { - left: 90%; - } + // &.increment-30 { + // left: 30%; + // } - &.increment-100 { - left: 100%; - } - } - } + // &.increment-40 { + // left: 40%; + // } - .grades { - position: relative; + // &.increment-50 { + // left: 50%; + // } - li { - position: absolute; - top: 0; - height: 40px; - text-align: right; + // &.increment-60 { + // left: 60%; + // } - &:hover, - &.is-dragging { - .remove-button { - display: block; - } - } + // &.increment-70 { + // left: 70%; + // } - .remove-button { - display: none; - position: absolute; - top: -17px; - right: 1px; - height: 17px; - font-size: 10px; - } + // &.increment-80 { + // left: 80%; + // } - &:nth-child(1) { - background: #4fe696; - } + // &.increment-90 { + // left: 90%; + // } - &:nth-child(2) { - background: #ffdf7e; - } + // &.increment-100 { + // left: 100%; + // } + // } + // } - &:nth-child(3) { - background: #ffb657; - } + // .grades { + // position: relative; - &:nth-child(4) { - background: #fb336c; - } + // li { + // position: absolute; + // top: 0; + // height: 40px; + // text-align: right; - &:nth-child(5) { - background: #ef54a1; - } + // &:hover, + // &.is-dragging { + // .remove-button { + // display: block; + // } + // } - .letter-grade { - display: block; - margin: 7px 5px 0 0; - font-size: 14px; - font-weight: 700; - line-height: 14px; - } + // .remove-button { + // display: none; + // position: absolute; + // top: -17px; + // right: 1px; + // height: 17px; + // font-size: 10px; + // } - .range { - display: block; - margin-right: 5px; - font-size: 9px; - line-height: 12px; - } + // &:nth-child(1) { + // background: #4fe696; + // } - .drag-bar { - position: absolute; - top: 0; - right: -1px; - height: 40px; - width: 2px; - background-color: #fff; - cursor: ew-resize; - @include transition(none); + // &:nth-child(2) { + // background: #ffdf7e; + // } - &:hover { - width: 4px; - right: -2px; - } - } - } - } - } - } + // &:nth-child(3) { + // background: #ffb657; + // } + + // &:nth-child(4) { + // background: #fb336c; + // } + + // &:nth-child(5) { + // background: #ef54a1; + // } + + // .letter-grade { + // display: block; + // margin: 7px 5px 0 0; + // font-size: 14px; + // font-weight: 700; + // line-height: 14px; + // } + + // .range { + // display: block; + // margin-right: 5px; + // font-size: 9px; + // line-height: 12px; + // } + + // .drag-bar { + // position: absolute; + // top: 0; + // right: -1px; + // height: 40px; + // width: 2px; + // background-color: #fff; + // cursor: ew-resize; + // @include transition(none); + + // &:hover { + // width: 4px; + // right: -2px; + // } + // } + // } + // } + // } + // } } \ No newline at end of file diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 77800f99c5..b7753674bf 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -15,6 +15,12 @@ var gradeThresholds; var GRADES = ['A', 'B', 'C', 'D', 'E']; + $(" :input, textarea").focus(function() { + $("label[for='" + this.id + "']").addClass("is-focused"); + }).blur(function() { + $("label").removeClass("is-focused"); + }); + (function() { $body = $('body'); $gradeBar = $('.grade-bar'); @@ -82,6 +88,7 @@ <%block name="content"> +

    Settings

    @@ -90,9 +97,8 @@
    -
    -

    Course Details

    +
    +

    Course Details

    Basic Information

    - The nuts and bolts of your class + The nuts and bolts of your course
    - - -
    -
    - - -
    -
    - - - e.g. 101x -
    -
    - - -
    -
    - -
    -
    -

    Dates & Times

    - The nuts and bolts of your class -
    - -
    - - - First day the class begins -
    - -
    - - - Last day of class activty -
    - -
    - - +
    -
      -
    1. -
      - - Milestone Date + +
      +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + + e.g. 101x +
    +
    +
    +
    + +
    + +
    +
    +

    Course Schedule

    + Important steps and segments of your your course +
    + +
    + +
    +
    + + First day the class begins +
    +
    +
    + +
    + +
    +
    + + Last day the class begins +
    +
    +
    + +
    +

    Milestones:

    + +
    +
      +
    • +
      + +
      -
      - - Milestone Name +
      + +
      Delete Milestone
    • - + +
    • +
      + + +
      + +
      + + +
      +
    • +
    - New Class Milestone + New Course Milestone
    -
    -
    +
    -
    +
    + +
    +
    +
    +
    + + + Replace Syllabus + + PDF formatting preferred +
    + +
    + + Upload Syllabus + + PDF formatting preferred +
    +
    +
    +
    + +
    + +

    Introducing Your Course

    Information for perspective students
    - - -
    + +
    + + Detailed summary of concepts and lessons covered +
    +
    - - - Used to introduce your class to perspective students -
    + +
    + + 1-2 sentences used to introduce your class to perspective students +
    +
    - - -
    -
    + +
    +
    +
    + +
    + + + Replace Video + + Video restrictions go here +
    + +
    + + Upload Video + + Video restrictions go here +
    +
    + + + +
    @@ -203,68 +286,101 @@
    - - -
    - -
    - - +
    - -
      -
    1. -
      - -
      - - Delete Link -
    2. -
    - - - New Link - + + Supplies, software, and set-up that students will need
    -
    - -
    - -
    - - -
    + +
    + + Time students should spend on all course work +
    +
    - +

    Textbooks:

    -
    -
      -
    1. -
      - - Textbook Name +
      +
    + +
  • +
    + + +
    - +
    + + +
    + +
  • + + + New Textbook
    -
    + + +
    +

    Prerequisites:

    + +
    +
      +
    • +
      + + +
      + +
      + + +
      + + Delete Prerequisite +
    • + +
    • +
      + + +
      + +
      + + +
      +
    • +
    + + + New Prerequisite + +
    +
    +
    +

    More Information

    @@ -272,24 +388,38 @@
    - +

    FAQs:

    -
    -
      -
    1. -
      - - Question +
      +
    + +
  • +
    + + +
    + +
    + + +
    + + Delete Question & Answer +
  • + New Question & Answer @@ -297,149 +427,138 @@
    - - + -
    -

    Course Staff

    +
    +

    Faculty

    -
    +
    -

    Faculty

    +

    Faculty Members

    + Individuals instructing and help with this course
    -
      -
    1. -
      - - -
      -
      - - -
      -
      - - - This photo will appear on your course's info page -
      -
      - - - A brief description of your education, experience, and expertise -
      -
    2. -
    +
    + + + + New Faculty Member + +
    -
    + +
    -

    Grading

    -
    - -
    - -
    -
    -
      -
    1. 0
    2. -
    3. 10
    4. -
    5. 20
    6. -
    7. 30
    8. -
    9. 40
    10. -
    11. 50
    12. -
    13. 60
    14. -
    15. 70
    16. -
    17. 80
    18. -
    19. 90
    20. -
    21. 100
    22. -
    -
      -
    1. - A - 81-100 - remove -
    2. -
    3. - B - 71-80 - - remove -
    4. -
    5. - C - 0-70 - - remove -
    6. -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -

    Homework

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

    Lab

    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    +

    Grading

    -
    -

    Handouts & Guides

    - -
    - - - PDF formatted file -
    -
    +
    -

    Problems

    -
    - -
    -
    +

    Problems

    + +
    -

    Discussions

    +

    Discussions

    -
    + From 1a297674cedba64ff9b37f8bfd92ec1bdabfd19f Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 15 Nov 2012 14:31:16 -0500 Subject: [PATCH 011/243] added in fields for problems and discussions portions of CMS settings --- cms/static/sass/_settings.scss | 129 +++++++++++- cms/static/sass/_variables.scss | 1 + cms/templates/settings.html | 356 +++++++++++++++++++++++++++++--- 3 files changed, 457 insertions(+), 29 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 40d991937d..a809c76973 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -152,6 +152,74 @@ font-size: 13px; } + .field.ui-status { + + > .input { + display: block !important; + margin-bottom: 15px; + } + + .ui-status-input-checkbox, .ui-status-input-radio { + position: absolute; + top: -9999px; + left: -9999px; + } + + label { + cursor: pointer; + } + + .ui-status-input-checkbox ~ label, .ui-status-input-radio ~ label { + display: inline-block; + margin: 0 0 0 5px; + color: $offBlack; + opacity: 0.50; + cursor: pointer; + @include transition(opacity 0.25s ease-in-out); + + &:after { + content: "(Off)"; + display: inline-block; + margin-left: 10px; + } + + ~ .tip { + margin-top: 0; + @include transition(color 0.25s ease-in-out); + } + } + + .ui-status-indic { + position: relative; + top: 2px; + display: inline-block; + height: 15px; + width: 15px; + border: 2px; + background: $offBlack; + opacity: 0.50; + @include border-radius(50px); + @include box-sizing(border-box); + @include transition(opacity 0.25s ease-in-out); + } + + .ui-status-input-checkbox:checked ~ label, .ui-status-input-radio:checked ~ label { + opacity: 0.99; + + &:after { + content: "(On)"; + } + + ~ .tip { + color: $darkGrey; + } + } + + .ui-status-input-checkbox:checked ~ .ui-status-indic, .ui-status-input-radio:checked ~ .ui-status-indic { + opacity: 0.99; + } + } + .tip { color: $mediumGrey; font-size: 13px; @@ -174,7 +242,6 @@ > label, .label { display: inline-block; vertical-align: top; - width: 200px; } // tips @@ -191,7 +258,7 @@ // structural field, not semantic fields per se .field { display: inline-block; - width: 400px; + width: 100%; > input, > textarea, .input { display: inline-block; @@ -248,7 +315,6 @@ .group { float: left; margin-right: 20px; - width: 170px; &:nth-child(2) { margin-right: 0; @@ -283,6 +349,26 @@ &.enum { } } + + // layout - aligned label/field pairs + &.row-col2 { + + > label, .label { + width: 200px; + } + + .field { + width: 400px; + } + + &.multi-inline { + @include clearfix; + + .group { + width: 170px; + } + } + } } // editing controls - adding @@ -326,7 +412,6 @@ } } - // specific sections .settings-details { @@ -379,10 +464,23 @@ .settings-problems { + > section { + + &.is-shown { + display: block; + } + } } .settings-discussions { - + + } + + .settings-discussions-exceptions, .settings-problems-exceptions { + + p, ul { + font-size: 15px; + } } // states @@ -391,6 +489,27 @@ @include transition(color 1s ease-in-out); } + // extras/abbreviations + // .settings-extras { + + // > header { + // cursor: pointer; + + // &.active { + + // } + // } + + // > div { + // display: none; + // @include transition(display 0.25s ease-in-out); + + // &.is-shown { + // display: block; + // } + // } + // } + // misc .divide { display: none; diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index fec65e4e11..a234a0d47e 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -13,6 +13,7 @@ $body-line-height: golden-ratio(.875em, 1); $pink: rgb(182,37,104); $error-red: rgb(253, 87, 87); +$offBlack: #3c3c3c; $blue: #5597dd; $orange: #edbd3c; $lightGrey: #edf1f5; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index b7753674bf..bbfcbc6dbd 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -26,6 +26,7 @@ $gradeBar = $('.grade-bar'); gradeThresholds = [100, 80, 70]; $('.settings-page-menu a').bind('click', showSettingsTab); + $('.settings-extra header').bind('click', showSettingsExtras); $body.on('mousedown', '.drag-bar', startDragBar); $('.new-grade-button').bind('click', addNewGrade); $body.on('click', '.remove-button', removeGrade); @@ -52,6 +53,12 @@ $(e.target).addClass('is-shown'); } + function showSettingsExtras(e) { + e.preventDefault(); + $(this).toggleClass('active'); + $(this).siblings.toggleClass('is-shown'); + } + function startDragBar(e) { e.preventDefault(); barOrigin = $gradeBar.offset().left; @@ -115,21 +122,21 @@ The nuts and bolts of your course -
    +
    -
    +
    -
    +
    @@ -138,7 +145,7 @@
    - +
    @@ -148,7 +155,7 @@ Important steps and segments of your your course -
    +
    @@ -158,7 +165,7 @@
    -
    +
    @@ -168,7 +175,7 @@
    -
    +

    Milestones:

    @@ -206,7 +213,7 @@
    -
    +
    @@ -237,7 +244,7 @@ Information for perspective students -
    +
    @@ -245,7 +252,7 @@
    -
    +
    @@ -253,7 +260,7 @@
    -
    +
    @@ -285,7 +292,7 @@ Expectations of the students taking this course -
    +
    @@ -293,7 +300,7 @@
    -
    +
    @@ -301,7 +308,7 @@
    -
    +

    Textbooks:

    @@ -340,7 +347,7 @@
    -
    +

    Prerequisites:

    @@ -387,7 +394,7 @@ Other helpful information about the course -
    +

    FAQs:

    @@ -441,21 +448,21 @@
    • -
      +
      -
      +
      -
      +
      @@ -478,7 +485,7 @@
      -
      +
      @@ -490,21 +497,21 @@
    • -
      +
      -
      +
      -
      +
      @@ -527,7 +534,7 @@
      -
      +
      @@ -553,11 +560,312 @@

      Problems

      +
      +
      +

      General Settings

      + Course-wide settings for all problems +
      + +
      +

      Problem Randomization:

      + +
      +
      + +
      + + randomize all problems +
      + +
      + +
      + + do not randomize problems +
      + +
      + +
      + + randomize problems per student +
      +
      +
      + +
      +

      Show Answers:

      + +
      +
      + +
      + + Answers will be shown after the number of attempts has been met +
      + +
      + +
      + + Answers will never be shown, regardless of attempts +
      +
      +
      + +
      + + +
      +
      + + To set infinite atttempts, use "0" +
      +
      +
      +
      + +
      +
      +

      Lesson Exercises

      + In-lesson question & problem specific rules +
      + +
      +

      Problem Randomization:

      + +
      +
      + +
      + + randomize all problems +
      + +
      + +
      + + do not randomize problems +
      + +
      + +
      + + randomize problems per student +
      +
      +
      + +
      +

      Show Answers:

      + +
      +
      + +
      + + Answers will be shown after the number of attempts has been met +
      + +
      + +
      + + Answers will never be shown, regardless of attempts +
      +
      +
      + +
      + + +
      +
      + + To set infinite atttempts, use "0" +
      +
      +
      +
      + +
      +
      +

      Labs

      + Exploratory assignment-specific rules +
      + +
      +

      Problem Randomization:

      + +
      +
      + +
      + + randomize all problems +
      + +
      + +
      + + do not randomize problems +
      + +
      + +
      + + randomize problems per student +
      +
      +
      + +
      +

      Show Answers:

      + +
      +
      + +
      + + Answers will be shown after the number of attempts has been met +
      + +
      + +
      + + Answers will never be shown, regardless of attempts +
      +
      +
      + +
      + + +
      +
      + + To set infinite atttempts, use "0" +
      +
      +
      +
      + +
      +
      +

      Exams

      + Mid-term and final exam-specific rules +
      + +
      +

      Problem Randomization:

      + +
      +
      + +
      + + randomize all problems +
      + +
      + +
      + + do not randomize problems +
      + +
      + +
      + + randomize problems per student +
      +
      +
      + +
      +

      Show Answers:

      + +
      +
      + +
      + + Answers will be shown after the number of attempts has been met +
      + +
      + +
      + + Answers will never be shown, regardless of attempts +
      +
      +
      + +
      + + +
      +
      + + To set infinite atttempts, use "0" +
      +
      +
      +
      + +
      + +

      Discussions

      +
      +
      +

      General Settings

      + Course-wide settings for online discussion +
      + +
      +

      Anonymous Discussions:

      + +
      +
      + +
      + + Students and faculty will be able to post anonymously +
      + +
      + +
      + + Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous +
      +
      +
      + +
      +
      +

      Discussion Settings Elsewhere

      + More specific settings for particular discussions +
      + +

      The following discussions have had their settings specified elsewhere and will follow those rules.

      + +
        + Discussion +
      +
      From 4c220ea420fba53bd0df4c512bcf36466a0cdd1b Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 15 Nov 2012 16:45:16 -0500 Subject: [PATCH 012/243] in progress discussion work --- cms/static/sass/_settings.scss | 22 +++++++++++++--------- cms/templates/settings.html | 17 ----------------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index a809c76973..9897043bd4 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -170,15 +170,23 @@ } .ui-status-input-checkbox ~ label, .ui-status-input-radio ~ label { + position: relative; + left: -30px; display: inline-block; + z-index: 100; margin: 0 0 0 5px; + padding-left: 30px; color: $offBlack; opacity: 0.50; cursor: pointer; @include transition(opacity 0.25s ease-in-out); + &:before { + display: inline-block; + margin-right: 10px; + } + &:after { - content: "(Off)"; display: inline-block; margin-left: 10px; } @@ -192,6 +200,7 @@ .ui-status-indic { position: relative; top: 2px; + z-index: 10; display: inline-block; height: 15px; width: 15px; @@ -207,7 +216,9 @@ opacity: 0.99; &:after { - content: "(On)"; + } + + &:before { } ~ .tip { @@ -476,13 +487,6 @@ } - .settings-discussions-exceptions, .settings-problems-exceptions { - - p, ul { - font-size: 15px; - } - } - // states label.is-focused { color: $blue; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index bbfcbc6dbd..102e57ebec 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -819,10 +819,6 @@
      - -
      - -
      @@ -853,19 +849,6 @@
      - -
      -
      -

      Discussion Settings Elsewhere

      - More specific settings for particular discussions -
      - -

      The following discussions have had their settings specified elsewhere and will follow those rules.

      - -
        - Discussion -
      -
      From 74dec7f172019888f13818b5396d393d2a492cf4 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 16 Nov 2012 09:14:18 -0500 Subject: [PATCH 013/243] settings - wip --- cms/static/sass/_settings.scss | 300 ++++++++++++++++----------------- cms/templates/settings.html | 273 +++++++++++++++++++++--------- 2 files changed, 341 insertions(+), 232 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 9897043bd4..4042edb5e9 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -522,185 +522,185 @@ - // h3 { - // margin-bottom: 30px; - // font-size: 15px; - // font-weight: 700; - // color: $blue; - // } + h3 { + margin-bottom: 30px; + font-size: 15px; + font-weight: 700; + color: $blue; + } - // .grade-controls { - // @include clearfix; - // } + .grade-controls { + @include clearfix; + } - // .new-grade-button { - // position: relative; - // float: left; - // display: block; - // width: 29px; - // height: 29px; - // margin: 4px 10px 0 0; - // border-radius: 20px; - // border: 1px solid $darkGrey; - // @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); - // background-color: #d1dae3; - // @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); - // color: #6d788b; + .new-grade-button { + position: relative; + float: left; + display: block; + width: 29px; + height: 29px; + margin: 4px 10px 0 0; + border-radius: 20px; + border: 1px solid $darkGrey; + @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); + background-color: #d1dae3; + @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); + color: #6d788b; - // .plus-icon { - // position: absolute; - // top: 50%; - // left: 50%; - // margin-left: -6px; - // margin-top: -6px; - // } - // } + .plus-icon { + position: absolute; + top: 50%; + left: 50%; + margin-left: -6px; + margin-top: -6px; + } + } - // .grade-slider { - // float: left; - // width: 560px; - // height: 60px; + .grade-slider { + float: left; + width: 560px; + height: 60px; - // .grade-bar { - // position: relative; - // width: 100%; - // height: 40px; - // background: $lightGrey; + .grade-bar { + position: relative; + width: 100%; + height: 40px; + background: $lightGrey; - // .increments { - // position: relative; + .increments { + position: relative; - // li { - // position: absolute; - // top: 42px; - // width: 30px; - // margin-left: -15px; - // font-size: 9px; - // text-align: center; + li { + position: absolute; + top: 42px; + width: 30px; + margin-left: -15px; + font-size: 9px; + text-align: center; - // &.increment-0 { - // left: 0; - // } + &.increment-0 { + left: 0; + } - // &.increment-10 { - // left: 10%; - // } + &.increment-10 { + left: 10%; + } - // &.increment-20 { - // left: 20%; - // } + &.increment-20 { + left: 20%; + } - // &.increment-30 { - // left: 30%; - // } + &.increment-30 { + left: 30%; + } - // &.increment-40 { - // left: 40%; - // } + &.increment-40 { + left: 40%; + } - // &.increment-50 { - // left: 50%; - // } + &.increment-50 { + left: 50%; + } - // &.increment-60 { - // left: 60%; - // } + &.increment-60 { + left: 60%; + } - // &.increment-70 { - // left: 70%; - // } + &.increment-70 { + left: 70%; + } - // &.increment-80 { - // left: 80%; - // } + &.increment-80 { + left: 80%; + } - // &.increment-90 { - // left: 90%; - // } + &.increment-90 { + left: 90%; + } - // &.increment-100 { - // left: 100%; - // } - // } - // } + &.increment-100 { + left: 100%; + } + } + } - // .grades { - // position: relative; + .grades { + position: relative; - // li { - // position: absolute; - // top: 0; - // height: 40px; - // text-align: right; + li { + position: absolute; + top: 0; + height: 40px; + text-align: right; - // &:hover, - // &.is-dragging { - // .remove-button { - // display: block; - // } - // } + &:hover, + &.is-dragging { + .remove-button { + display: block; + } + } - // .remove-button { - // display: none; - // position: absolute; - // top: -17px; - // right: 1px; - // height: 17px; - // font-size: 10px; - // } + .remove-button { + display: none; + position: absolute; + top: -17px; + right: 1px; + height: 17px; + font-size: 10px; + } - // &:nth-child(1) { - // background: #4fe696; - // } + &:nth-child(1) { + background: #4fe696; + } - // &:nth-child(2) { - // background: #ffdf7e; - // } + &:nth-child(2) { + background: #ffdf7e; + } - // &:nth-child(3) { - // background: #ffb657; - // } + &:nth-child(3) { + background: #ffb657; + } - // &:nth-child(4) { - // background: #fb336c; - // } + &:nth-child(4) { + background: #fb336c; + } - // &:nth-child(5) { - // background: #ef54a1; - // } + &:nth-child(5) { + background: #ef54a1; + } - // .letter-grade { - // display: block; - // margin: 7px 5px 0 0; - // font-size: 14px; - // font-weight: 700; - // line-height: 14px; - // } + .letter-grade { + display: block; + margin: 7px 5px 0 0; + font-size: 14px; + font-weight: 700; + line-height: 14px; + } - // .range { - // display: block; - // margin-right: 5px; - // font-size: 9px; - // line-height: 12px; - // } + .range { + display: block; + margin-right: 5px; + font-size: 9px; + line-height: 12px; + } - // .drag-bar { - // position: absolute; - // top: 0; - // right: -1px; - // height: 40px; - // width: 2px; - // background-color: #fff; - // cursor: ew-resize; - // @include transition(none); + .drag-bar { + position: absolute; + top: 0; + right: -1px; + height: 40px; + width: 2px; + background-color: #fff; + cursor: ew-resize; + @include transition(none); - // &:hover { - // width: 4px; - // right: -2px; - // } - // } - // } - // } - // } - // } + &:hover { + width: 4px; + right: -2px; + } + } + } + } + } + } } \ No newline at end of file diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 102e57ebec..0177e9862a 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -445,108 +445,110 @@ Individuals instructing and help with this course -
      - - - New Faculty Member - + + New Faculty Member + +
      @@ -555,6 +557,113 @@

      Grading

      +
      +
      +

      Overall Grade Range

      + Course grade ranges and their values +
      + +
      + +
      + +
      +
      +
        +
      1. 0
      2. +
      3. 10
      4. +
      5. 20
      6. +
      7. 30
      8. +
      9. 40
      10. +
      11. 50
      12. +
      13. 60
      14. +
      15. 70
      16. +
      17. 80
      18. +
      19. 90
      20. +
      21. 100
      22. +
      +
        +
      1. + A + 81-100 + remove +
      2. +
      3. + B + 71-80 + + remove +
      4. +
      5. + C + 0-70 + + remove +
      6. +
      +
      +
      +
      + +
      +
      + +
      +
      +

      Assignments

      + Course grade ranges and their values +
      + + +
      + +
      +
      +

      Assignments

      + Course grade ranges and their values +
      + + +
      + +
      +
      + + +
      +
      +
      +

      Homework

      +
      + + +
      +
      + + +
      +
      + + +
      +
      +
      +

      Lab

      +
      + + +
      +
      + + +
      +
      + + +
      +
      + +
      From 6f52fea09cb098016831d671112d7b5107d9f676 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 16 Nov 2012 09:23:33 -0500 Subject: [PATCH 014/243] added and wired in SymbolSet font icon library assets --- cms/templates/base.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cms/templates/base.html b/cms/templates/base.html index ba91b2f400..33fe02aadd 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -9,6 +9,9 @@ <%static:css group='base-style'/> + + + <%block name="title"></%block> @@ -33,6 +36,9 @@ + + + + + + + + +<%block name="content"> +
      +
      +

      Course Info

      +
      +
      +

      Updates

      + New Update +
      + +
      +
      + +
      +
      + \ No newline at end of file diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 877f03533c..73ce3f0604 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -10,6 +10,7 @@ ${context_course.display_name}
      • Courseware
      • +
      • Course Info
      • Tabs
      • Assets
      • Users
      • diff --git a/cms/urls.py b/cms/urls.py index e0dbc68129..4c2f2b361e 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -34,7 +34,12 @@ urlpatterns = ('', 'contentstore.views.remove_user', name='remove_user'), url(r'^(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)/remove_user$', 'contentstore.views.remove_user', name='remove_user'), - url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', name='static_pages'), + url(r'^(?P[^/]+)/(?P[^/]+)/info/(?P[^/]+)$', 'contentstore.views.course_info', name='course_info'), + # ??? Is the following necessary or will the one below work w/ id=None if not sent? + # url(r'^(?P[^/]+)/(?P[^/]+)/course_info/updates$', 'contentstore.views.course_info_updates', name='course_info'), + url(r'^(?P[^/]+)/(?P[^/]+)/course_info/updates/(?P.*)$', 'contentstore.views.course_info_updates', name='course_info'), + url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', + name='static_pages'), url(r'^edit_static/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_static', name='edit_static'), url(r'^edit_tabs/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_tabs', name='edit_tabs'), url(r'^(?P[^/]+)/(?P[^/]+)/assets/(?P[^/]+)$', 'contentstore.views.asset_index', name='asset_index'), From 842bbe92d13c6ce64dcc1b4460d93bc4b4b0fc26 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Fri, 16 Nov 2012 12:10:39 -0500 Subject: [PATCH 019/243] Everything tested and ready for Tom to fix --- .../contentstore/course_info_model.py | 148 ++++++++++++++++++ cms/static/coffee/files.json | 2 +- .../client_templates/course_info_update.html | 28 ++++ .../src/client_templates/load_templates.html | 14 ++ .../coffee/src/views/course_info_edit.coffee | 63 -------- cms/static/js/models/course_info.js | 34 ++++ cms/static/js/template_loader.js | 77 +++++++++ cms/static/js/views/course_info_edit.js | 138 ++++++++++++++++ cms/templates/course_info.html | 33 ++-- .../xmodule/templates/courseinfo/empty.yaml | 2 +- 10 files changed, 465 insertions(+), 74 deletions(-) create mode 100644 cms/djangoapps/contentstore/course_info_model.py create mode 100644 cms/static/coffee/src/client_templates/course_info_update.html create mode 100644 cms/static/coffee/src/client_templates/load_templates.html delete mode 100644 cms/static/coffee/src/views/course_info_edit.coffee create mode 100644 cms/static/js/models/course_info.js create mode 100644 cms/static/js/template_loader.js create mode 100644 cms/static/js/views/course_info_edit.js diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py new file mode 100644 index 0000000000..87dfa5da8f --- /dev/null +++ b/cms/djangoapps/contentstore/course_info_model.py @@ -0,0 +1,148 @@ +from xmodule.modulestore.exceptions import ItemNotFoundError +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from lxml import etree +import re +from django.http import HttpResponseBadRequest + +## TODO store as array of { date, content } and override course_info_module.definition_from_xml +## This should be in a class which inherits from XmlDescriptor +def get_course_updates(location): + """ + Retrieve the relevant course_info updates and unpack into the model which the client expects: + [{id : location.url() + idx to make unique, date : string, content : html string}] + """ + try: + course_updates = modulestore('direct').get_item(location) + except ItemNotFoundError: + template = Location(['i4x', 'edx', "templates", 'course_info', "Empty"]) + course_updates = modulestore('direct').clone_item(template, Location(location)) + + # current db rep: {"_id" : locationjson, "definition" : { "data" : "
          [
        1. date

          content
        2. ]
        "} "metadata" : ignored} + location_base = course_updates.location.url() + + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. + try: + course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + course_html_parsed = etree.fromstring("
          ") + + # Confirm that root is
            , iterate over
          1. , pull out

            subs and then rest of val + course_upd_collection = [] + if course_html_parsed.tag == 'ol': + # 0 is the oldest so that new ones get unique idx + for idx, update in enumerate(course_html_parsed.iter("li")): + if (len(update) == 0): + continue + elif (len(update) == 1): + content = update.find("h2").tail + else: + content = etree.tostring(update[1]) + + course_upd_collection.append({"id" : location_base + "/" + str(idx), + "date" : update.findtext("h2"), + "content" : content}) + # return newest to oldest + course_upd_collection.reverse() + return course_upd_collection + +def update_course_updates(location, update, passed_id=None): + """ + Either add or update the given course update. It will add it if the passed_id is absent or None. It will update it if + it has an passed_id which has a valid value. Until updates have distinct values, the passed_id is the location url + an index + into the html structure. + """ + try: + course_updates = modulestore('direct').get_item(location) + except ItemNotFoundError: + return HttpResponseBadRequest + + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. + try: + course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + course_html_parsed = etree.fromstring("
              ") + + try: + new_html_parsed = etree.fromstring(update['content'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + new_html_parsed = None + + # Confirm that root is
                , iterate over
              1. , pull out

                subs and then rest of val + if course_html_parsed.tag == 'ol': + # ??? Should this use the id in the json or in the url or does it matter? + if passed_id: + element = course_html_parsed.findall("li")[get_idx(passed_id)] + element[0].text = update['date'] + if (len(element) == 1): + if new_html_parsed is not None: + element[0].tail = None + element.append(new_html_parsed) + else: + element[0].tail = update['content'] + else: + if new_html_parsed is not None: + element[1] = new_html_parsed + else: + element.pop(1) + element[0].tail = update['content'] + else: + idx = len(course_html_parsed.findall("li")) + passed_id = course_updates.location.url() + "/" + str(idx) + element = etree.SubElement(course_html_parsed, "li") + date_element = etree.SubElement(element, "h2") + date_element.text = update['date'] + if new_html_parsed is not None: + element[1] = new_html_parsed + else: + date_element.tail = update['content'] + + # update db record + course_updates.definition['data'] = etree.tostring(course_html_parsed) + modulestore('direct').update_item(location, course_updates.definition['data']) + + return {"id" : passed_id, + "date" : update['date'], + "content" :update['content']} + +def delete_course_update(location, update, passed_id): + """ + Delete the given course_info update from the db. + Returns the resulting course_updates b/c their ids change. + """ + if not passed_id: + return HttpResponseBadRequest + + try: + course_updates = modulestore('direct').get_item(location) + except ItemNotFoundError: + return HttpResponseBadRequest + + # TODO use delete_blank_text parser throughout and cache as a static var in a class + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. + try: + course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + course_html_parsed = etree.fromstring("
                  ") + + if course_html_parsed.tag == 'ol': + # ??? Should this use the id in the json or in the url or does it matter? + element_to_delete = course_html_parsed.xpath('/ol/li[position()=' + str(get_idx(passed_id) + 1) + "]") + if element_to_delete: + course_html_parsed.remove(element_to_delete[0]) + + # update db record + course_updates.definition['data'] = etree.tostring(course_html_parsed) + store = modulestore('direct') + store.update_item(location, course_updates.definition['data']) + + return get_course_updates(location) + +def get_idx(passed_id): + """ + From the url w/ idx appended, get the idx. + """ + # TODO compile this regex into a class static and reuse for each call + idx_matcher = re.search(r'.*/(\d)+$', passed_id) + if idx_matcher: + return int(idx_matcher.group(1)) \ No newline at end of file diff --git a/cms/static/coffee/files.json b/cms/static/coffee/files.json index b396bec944..7b2719a047 100644 --- a/cms/static/coffee/files.json +++ b/cms/static/coffee/files.json @@ -3,6 +3,6 @@ "/static/js/vendor/jquery.min.js", "/static/js/vendor/json2.js", "/static/js/vendor/underscore-min.js", - "/static/js/vendor/backbone-min.js" + "/static/js/vendor/backbone.js" ] } diff --git a/cms/static/coffee/src/client_templates/course_info_update.html b/cms/static/coffee/src/client_templates/course_info_update.html new file mode 100644 index 0000000000..54a4a38dde --- /dev/null +++ b/cms/static/coffee/src/client_templates/course_info_update.html @@ -0,0 +1,28 @@ +
                1. + +
                  +
                  + + + +
                  +
                  + +
                  +
                  + + Save + Cancel +
                  +
                  +

                  + <%= + updateModel.get('date') %> +

                  +
                  <%= updateModel.get('content') %>
                  +
                  + Edit + Delete +
                  +
                2. \ No newline at end of file diff --git a/cms/static/coffee/src/client_templates/load_templates.html b/cms/static/coffee/src/client_templates/load_templates.html new file mode 100644 index 0000000000..3ff88d6fe5 --- /dev/null +++ b/cms/static/coffee/src/client_templates/load_templates.html @@ -0,0 +1,14 @@ + + +<%block name="jsextra"> + + + + \ No newline at end of file diff --git a/cms/static/coffee/src/views/course_info_edit.coffee b/cms/static/coffee/src/views/course_info_edit.coffee deleted file mode 100644 index 49cb90c47d..0000000000 --- a/cms/static/coffee/src/views/course_info_edit.coffee +++ /dev/null @@ -1,63 +0,0 @@ -## Derived from and should inherit from a common ancestor w/ ModuleEdit -class CMS.Views.CourseInfoEdit extends Backbone.View - tagName: 'div' - className: 'component' - - events: - "click .component-editor .cancel-button": 'clickCancelButton' - "click .component-editor .save-button": 'clickSaveButton' - "click .component-actions .edit-button": 'clickEditButton' - "click .component-actions .delete-button": 'onDelete' - - initialize: -> - @render() - - $component_editor: => @$el.find('.component-editor') - - loadDisplay: -> - XModule.loadModule(@$el.find('.xmodule_display')) - - loadEdit: -> - if not @module - @module = XModule.loadModule(@$el.find('.xmodule_edit')) - - # don't show metadata (deprecated for course_info) - render: -> - if @model.id - @$el.load("/preview_component/#{@model.id}", => - @loadDisplay() - @delegateEvents() - ) - - clickSaveButton: (event) => - event.preventDefault() - data = @module.save() - @model.save(data).done( => - # # showToastMessage("Your changes have been saved.", null, 3) - @module = null - @render() - @$el.removeClass('editing') - ).fail( -> - showToastMessage("There was an error saving your changes. Please try again.", null, 3) - ) - - clickCancelButton: (event) -> - event.preventDefault() - @$el.removeClass('editing') - @$component_editor().slideUp(150) - - clickEditButton: (event) -> - event.preventDefault() - @$el.addClass('editing') - @$component_editor().slideDown(150) - @loadEdit() - - onDelete: (event) -> - # clear contents, don't delete - @model.definition.data = "
                    " - # TODO change label to 'clear' - - onNew: (event) -> - ele = $(@model.definition.data).find("ol") - if (ele) - ele = $(ele).first().prepend("
                  1. " + $.datepicker.formatDate('MM d', new Date()) + "

                    /n
                  2. "); \ No newline at end of file diff --git a/cms/static/js/models/course_info.js b/cms/static/js/models/course_info.js new file mode 100644 index 0000000000..b1d10c8d16 --- /dev/null +++ b/cms/static/js/models/course_info.js @@ -0,0 +1,34 @@ +// single per course holds the updates and handouts +CMS.Models.CourseInfo = Backbone.Model.extend({ + // This model class is not suited for restful operations and is considered just a server side initialized container + url: '', + + defaults: { + "courseId": "", // the location url + "updates" : null, // UpdateCollection + "handouts": null // HandoutCollection + }, + + idAttribute : "courseId" +}); + +// course update -- biggest kludge here is the lack of a real id to map updates to originals +CMS.Models.CourseUpdate = Backbone.Model.extend({ + defaults: { + "date" : $.datepicker.formatDate('MM d', new Date()), + "content" : "" + } +}); + +/* + The intitializer of this collection must set id to the update's location.url and courseLocation to the course's location. Must pass the + collection of updates as [{ date : "month day", content : "html"}] +*/ +CMS.Models.CourseUpdateCollection = Backbone.Collection.extend({ + url : function() {return this.urlbase + "course_info/updates/";}, + + model : CMS.Models.CourseUpdate +}); + + + \ No newline at end of file diff --git a/cms/static/js/template_loader.js b/cms/static/js/template_loader.js new file mode 100644 index 0000000000..b266575b7a --- /dev/null +++ b/cms/static/js/template_loader.js @@ -0,0 +1,77 @@ +// +// TODO Figure out how to initialize w/ static views from server (don't call .load but instead inject in django as strings) +// so this only loads the lazily loaded ones. +(function() { + if (typeof window.templateLoader == 'function') return; + + var templateLoader = { + templateVersion: "0.0.3", + templates: {}, + loadRemoteTemplate: function(templateName, filename, callback) { + if (!this.templates[templateName]) { + var self = this; + jQuery.ajax({url : filename, + success : function(data) { + self.addTemplate(templateName, data); + self.saveLocalTemplates(); + callback(data); + }, + error : function(xhdr, textStatus, errorThrown) { + console.log(textStatus); }, + dataType : "html" + }) + } + else { + callback(this.templates[templateName]); + } + }, + + addTemplate: function(templateName, data) { + // is there a reason this doesn't go ahead and compile the template? _.template(data) + // I suppose localstorage use would still req raw string rather than compiled version, but that sd work + // if it maintains a separate cache of uncompiled ones + this.templates[templateName] = data; + }, + + localStorageAvailable: function() { + try { + return 'localStorage' in window && window['localStorage'] !== null; + } catch (e) { + return false; + } + }, + + saveLocalTemplates: function() { + if (this.localStorageAvailable) { + localStorage.setItem("templates", JSON.stringify(this.templates)); + localStorage.setItem("templateVersion", this.templateVersion); + } + }, + + loadLocalTemplates: function() { + if (this.localStorageAvailable) { + var templateVersion = localStorage.getItem("templateVersion"); + if (templateVersion && templateVersion == this.templateVersion) { + var templates = localStorage.getItem("templates"); + if (templates) { + templates = JSON.parse(templates); + for (var x in templates) { + if (!this.templates[x]) { + this.addTemplate(x, templates[x]); + } + } + } + } + else { + localStorage.removeItem("templates"); + localStorage.removeItem("templateVersion"); + } + } + } + + + + }; + templateLoader.loadLocalTemplates(); + window.templateLoader = templateLoader; + })(); diff --git a/cms/static/js/views/course_info_edit.js b/cms/static/js/views/course_info_edit.js new file mode 100644 index 0000000000..4f504d72e8 --- /dev/null +++ b/cms/static/js/views/course_info_edit.js @@ -0,0 +1,138 @@ +/* this view should own everything on the page which has controls effecting its operation + generate other views for the individual editors. + The render here adds views for each update/handout by delegating to their collections but does not + generate any html for the surrounding page. +*/ +CMS.Views.CourseInfoEdit = Backbone.View.extend({ + // takes CMS.Models.CourseInfo as model + tagName: 'div', + + render: function() { + // instantiate the ClassInfoUpdateView and delegate the proper dom to it + new CMS.Views.ClassInfoUpdateView({ + el: this.$('#course-update-view'), + collection: this.model.get('updates') + }); + // TODO instantiate the handouts view + return this; + } +}); + +// ??? Programming style question: should each of these classes be in separate files? +CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ + // collection is CourseUpdateCollection + events: { + "click .new-update-button" : "onNew", + "click .save-button" : "onSave", + "click .cancel-button" : "onCancel", + "click .edit-button" : "onEdit", + "click .delete-button" : "onDelete" + }, + + initialize: function() { + var self = this; + // instantiates an editor template for each update in the collection + window.templateLoader.loadRemoteTemplate("course_info_update", + // TODO Where should the template reside? how to use the static.url to create the path? + "/static/coffee/src/client_templates/course_info_update.html", + function (raw_template) { + self.template = _.template(raw_template); + self.render(); + } + ); + }, + + render: function () { + // iterate over updates and create views for each using the template + var updateEle = this.$el.find("#course-update-list"); + // remove and then add all children + $(updateEle).empty(); + var self = this; + this.collection.each(function (update) { + var newEle = self.template({ updateModel : update }); + $(updateEle).append(newEle); + }); + this.$el.find(".new-update-form").hide(); + return this; + }, + + onNew: function(event) { + // create new obj, insert into collection, and render this one ele overriding the hidden attr + var newModel = new CMS.Models.CourseUpdate(); + this.collection.add(newModel, {at : 0}); + + var newForm = this.template({ updateModel : newModel }); + var updateEle = this.$el.find("#course-update-list"); + $(updateEle).append(newForm); + $(newForm).find(".new-update-form").show(); + }, + + onSave: function(event) { + var targetModel = this.eventModel(event); + targetModel.set({ date : this.dateEntry(event).val(), content : this.contentEntry(event).val() }); + // push change to display, hide the editor, submit the change + $(this.dateDisplay(event)).val(targetModel.get('date')); + $(this.contentDisplay(event)).val(targetModel.get('content')); + $(this.editor(event)).hide(); + + targetModel.save(); + }, + + onCancel: function(event) { + // change editor contents back to model values and hide the editor + $(this.editor(event)).hide(); + var targetModel = this.eventModel(event); + $(this.dateEntry(event)).val(targetModel.get('date')); + $(this.contentEntry(event)).val(targetModel.get('content')); + }, + + onEdit: function(event) { + $(this.editor(event)).show(); + }, + + onDelete: function(event) { + // TODO ask for confirmation + // remove the dom element and delete the model + var targetModel = this.eventModel(event); + this.modelDom(event).remove(); + var cacheThis = this; + targetModel.destroy({success : function (model, response) { + cacheThis.collection.fetch({success : function() {cacheThis.render();}}); + } + }); + }, + + // Dereferencing from events to screen elements + eventModel: function(event) { + // not sure if it should be currentTarget or delegateTarget + return this.collection.getByCid($(event.currentTarget).attr("name")); + }, + + modelDom: function(event) { + return $(event.currentTarget).closest("li"); + }, + + editor: function(event) { + var li = $(event.currentTarget).closest("li"); + if (li) return $(li).find("form").first(); + }, + + dateEntry: function(event) { + var li = $(event.currentTarget).closest("li"); + if (li) return $(li).find("#date-entry").first(); + }, + + contentEntry: function(event) { + return $(event.currentTarget).closest("li").find(".new-update-content").first(); + }, + + dateDisplay: function(event) { + return $(event.currentTarget).closest("li").find("#date-display").first(); + }, + + contentDisplay: function(event) { + return $(event.currentTarget).closest("li").find(".update-contents").first(); + } + +}); + \ No newline at end of file diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html index 72facf7c2e..f68aff008b 100644 --- a/cms/templates/course_info.html +++ b/cms/templates/course_info.html @@ -1,16 +1,31 @@ <%inherit file="base.html" /> +<%namespace name='static' file='static_content.html'/> + <%block name="title">Course Info <%block name="jsextra"> + + + + @@ -19,10 +34,10 @@ $(document).ready(function(){

                    Course Info

                    -
                    +

                    Updates

                    New Update -
                    +
                      diff --git a/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml b/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml index fa3ed606bd..c6958ed887 100644 --- a/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml +++ b/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml @@ -1,5 +1,5 @@ --- metadata: display_name: Empty -data: "

                      This is where you can add additional information about your course.

                      " +data: "
                        " children: [] \ No newline at end of file From 731ea2268759319054fa5a74a653ccedcc69ff31 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 07:19:13 -0500 Subject: [PATCH 020/243] settings - added disabled states for course name/org/number --- cms/static/sass/_settings.scss | 36 ++++++++++++++++++++++------------ cms/templates/settings.html | 19 +++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 818a3b3191..b600ed169a 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -141,6 +141,11 @@ border-color: $blue; outline: 0; } + + &:disabled { + color: $darkGrey; + background: $lightGrey; + } } ::-webkit-input-placeholder { @@ -564,14 +569,14 @@ } .grade-slider { - float: left; - width: 560px; - height: 60px; + float: right; + width: 95%; + height: 80px; .grade-bar { position: relative; width: 100%; - height: 40px; + height: 50px; background: $lightGrey; .increments { @@ -579,7 +584,7 @@ li { position: absolute; - top: 42px; + top: 52px; width: 30px; margin-left: -15px; font-size: 9px; @@ -637,7 +642,7 @@ li { position: absolute; top: 0; - height: 40px; + height: 50px; text-align: right; &:hover, @@ -647,6 +652,11 @@ } } + &.is-dragging { + + + } + .remove-button { display: none; position: absolute; @@ -678,16 +688,16 @@ .letter-grade { display: block; - margin: 7px 5px 0 0; - font-size: 14px; + margin: 10px 15px 0 0; + font-size: 16px; font-weight: 700; line-height: 14px; } .range { display: block; - margin-right: 5px; - font-size: 9px; + margin-right: 15px; + font-size: 10px; line-height: 12px; } @@ -695,14 +705,16 @@ position: absolute; top: 0; right: -1px; - height: 40px; + height: 50px; width: 2px; background-color: #fff; + @include box-shadow(-1px 0 3px rgba(0,0,0,0.1)); + cursor: ew-resize; @include transition(none); &:hover { - width: 4px; + width: 6px; right: -2px; } } diff --git a/cms/templates/settings.html b/cms/templates/settings.html index ef0ae0a1d0..58c9a7a9ce 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -36,7 +36,7 @@ $('.new-grade-button').bind('click', addNewGrade); $body.on('click', '.remove-button', removeGrade); - $('.set-date').datepicker(); + $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); })(); function addNewGrade(e) { @@ -133,7 +133,7 @@
                        - +
                        @@ -142,7 +142,7 @@
                        - +
                        @@ -151,7 +151,7 @@
                        - + e.g. 101x
                        @@ -170,7 +170,7 @@
                        - + First day the class begins
                        @@ -180,7 +180,7 @@
                        - + Last day the class begins
                        @@ -280,7 +280,6 @@

                      1. +
                      2. + F + 0-50 + + remove +

                From bf4f678bde3148675e8ea00637540de1337527ec Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 07:38:59 -0500 Subject: [PATCH 021/243] settings - revised faculty photo and syllabus states to either has or does not have --- cms/static/sass/_settings.scss | 12 ++++++++++++ cms/templates/settings.html | 21 ++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index b600ed169a..e6ee3ee251 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -467,6 +467,18 @@ .new-course-faculty-item { } + + .current-faculty-photo { + height: 115px; + width: 115px; + overflow: hidden; + + img { + display: block; + min-height: 100%; + max-width: 100%; + } + } } } diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 58c9a7a9ce..091da7246a 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -229,11 +229,11 @@
                + CS184x_syllabus.pdf
                - - Replace Syllabus - + Delete Syllabus + PDF formatting preferred
                @@ -280,12 +280,10 @@
                +
                - - Replace Video - - Video restrictions go here + Delete Video
                @@ -492,14 +490,11 @@
                From 0114366c369ddf6dd72bc0fc8eb4aca03fcb3044 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 08:05:52 -0500 Subject: [PATCH 022/243] settings - revised discussion settings UI to include standard discussion categories and adding new ones --- cms/static/sass/_settings.scss | 5 +++ cms/templates/settings.html | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index e6ee3ee251..89f7841df0 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -148,6 +148,11 @@ } } + .input-default { + color: $darkGrey; + background: $lightGrey; + } + ::-webkit-input-placeholder { color: $mediumGrey; font-size: 13px; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 091da7246a..60f1cc0f65 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -1069,8 +1069,82 @@ Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous
                +
                + +
                +
                +

                Discussion Categories

                + +
                + + + + New Discussion Category + +
                +
                + +
                +

                Create Discussion Categories per Unit

                + +
                +
                + +
                + + This option is automatically set currently +
                +
                +
                +
                From cff581d5e60425ade3fd51c5900c01ee68e5058d Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 08:16:48 -0500 Subject: [PATCH 023/243] settings - revised assignment due time field and provided UTC context/help --- cms/templates/settings.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 60f1cc0f65..13ae4fa6f3 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -318,7 +318,7 @@
                - + Time students should spend on all course work
                @@ -640,8 +640,9 @@
                - - The standard time of day assignments are due by + + Boston, MA Local Time (UTC/GMT -5 hours) + Convert to your time zone
                From 14f668a41ec732ad03553776b46265e8a8183280 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 08:35:02 -0500 Subject: [PATCH 024/243] settings - revised assignment grace period text --- cms/templates/settings.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 13ae4fa6f3..079ba25f51 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -648,12 +648,12 @@
                - +
                - - e.g. +5 minutes + + leeway on due dates
                From 8525b898d756a7f12ae5e72a4ab2d2f369d8406b Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 29 Oct 2012 15:12:15 -0400 Subject: [PATCH 025/243] Add migration to make columns added by askbot nullable, in preparation for eventually deleting them --- .../migrations/0020_prep_cleanup_askbot.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py diff --git a/common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py b/common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py new file mode 100644 index 0000000000..14cdf89d96 --- /dev/null +++ b/common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models +import datetime + +ASKBOT_AUTH_USER_COLUMNS = ( + 'website', + 'about', + 'gold', + 'email_isvalid', + 'real_name', + 'location', + 'reputation', + 'gravatar', + 'bronze', + 'last_seen', + 'silver', + 'questions_per_page', + 'new_response_count', + 'seen_response_count', +) + +class Migration(DataMigration): + def make_column_nullable(self, table, column): + field, args, kwargs = self.models[table][column] + if kwargs.get('default') == 'datetime.datetime.now': + kwargs['default'] = datetime.datetime.now + kwargs['null'] = True + db.alter_column(table.replace('.', '_'), column, self.gf(field)(*args, **kwargs)) + + def reset_column(self, table, column): + field, args, kwargs = self.models[table][column] + if kwargs.get('default') == 'datetime.datetime.now': + kwargs['default'] = datetime.datetime.now + db.alter_column(table.replace('.', '_'), column, self.gf(field)(*args, **kwargs)) + + def forwards(self, orm): + "Write your forwards methods here." + for column in ASKBOT_AUTH_USER_COLUMNS: + self.make_column_nullable('auth.user', column) + + def backwards(self, orm): + "Write your backwards methods here." + for column in ASKBOT_AUTH_USER_COLUMNS: + self.reset_column('auth.user', column) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'student.courseenrollment': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] + symmetrical = True From bc4d5f413d3da580c32c4b5e2395f68d37f47f64 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 09:54:08 -0500 Subject: [PATCH 026/243] settings - various style/alignment tweaks to inline/existinf fields --- cms/static/sass/_settings.scss | 44 ++++++++++++--- cms/static/sass/_variables.scss | 1 + cms/templates/settings.html | 94 ++++++++++++++++++++++----------- 3 files changed, 103 insertions(+), 36 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 89f7841df0..6c3413472c 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -129,6 +129,7 @@ } &.short { + min-width: 100px; width: 25%; } @@ -148,8 +149,8 @@ } } - .input-default { - color: $darkGrey; + .input-default input, .input-default textarea { + color: $mediumGrey; background: $lightGrey; } @@ -379,7 +380,7 @@ } .field { - width: 400px; + width: 400px ! important; } &.multi-inline { @@ -426,10 +427,10 @@ .current { width: 100%; margin: 10px 0; - padding: 15px; + padding: 10px; @include box-sizing(border-box); @include border-radius(5px); - background: tint($blue, 80%); + background: tint($lightGrey, 50%); } } @@ -464,6 +465,10 @@ border-bottom: none; } } + + .remove-faculty-photo { + display: inline-block; + } } #course-faculty-bio-input { @@ -493,7 +498,6 @@ .course-grading-gradeweight, .course-grading-totalassignments, .course-grading-totalassignmentsdroppable { input { - width: 73px; } } } @@ -514,6 +518,23 @@ .settings-discussions { + .settings-discussions-categories .course-discussions-categories-list-item { + + label { + display: none; + } + + .group { + display: inline-block; + } + + .remove-item { + display: inline-block !important; + margin-left: 10px; + } + } + + } // states @@ -543,6 +564,17 @@ // } // } + input.error, textarea.error { + border-color: $red; + } + + .message-error { + display: block; + margin-top: 5px; + color: $red; + font-size: 13px; + } + // misc .divide { display: none; diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index a234a0d47e..8ab5e0d703 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -16,6 +16,7 @@ $error-red: rgb(253, 87, 87); $offBlack: #3c3c3c; $blue: #5597dd; $orange: #edbd3c; +$red: #e23c3e; $lightGrey: #edf1f5; $mediumGrey: #ced2db; $darkGrey: #8891a1; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 079ba25f51..1cc0ae8ea0 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -171,7 +171,18 @@
                - First day the class begins + First day the course begins +
                +
                +
                + +
                + +
                +
                + + First day the course begins + The start date of the course cannot be after the end date of the course
                @@ -181,7 +192,18 @@
                - Last day the class begins + Last day the course begins +
                +
                +
                + +
                + +
                +
                + + Last day the course begins + The end date of the course cannot be before the start date of the course
                @@ -205,6 +227,36 @@ Delete Milestone +
              2. +
                + + + A milestone date cannot be after the end of a course +
                + +
                + + +
                + + Delete Milestone +
              3. + +
              4. +
                + + + A milestone date cannot be before the start of a course +
                + +
                + + +
                + + Delete Milestone +
              5. +
              6. @@ -233,8 +285,6 @@
                Delete Syllabus - - PDF formatting preferred
              7. @@ -259,7 +309,7 @@
                - + Detailed summary of concepts and lessons covered
                @@ -280,7 +330,7 @@
                - +
                Delete Video @@ -482,13 +532,6 @@
                -
                - - Upload Faculty Photo - - Max size: 30KB -
                -
                Faculty Photo @@ -534,17 +577,6 @@ Max size: 30KB
                - -
                -
                - -
                - - - Upload Faculty Photo - - Max size: 30KB -
                @@ -640,8 +672,8 @@
                - - Boston, MA Local Time (UTC/GMT -5 hours) + + Boston, MA Local Time (UTC/GMT -5 hours) Convert to your time zone
                @@ -1105,6 +1137,8 @@
                + + Delete Category
                @@ -1114,7 +1148,7 @@
                - Delete Discussion Category + Delete Category
              8. @@ -1123,7 +1157,7 @@
              9. - Delete Discussion Category + Delete Category @@ -1134,11 +1168,11 @@
                -

                Create Discussion Categories per Unit

                +

                Create Discussion
                Categories for Each
                Course Unit

                - +
                This option is automatically set currently From b18136d0cd59f5b0a8661e587e2fb8c1f68cf0c2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 30 Oct 2012 18:23:07 -0400 Subject: [PATCH 027/243] remove (almost) all references to askbot. --- cms/djangoapps/contentstore/views.py | 102 ++++++++++++--------------- lms/envs/aws.py | 4 -- lms/envs/common.py | 3 + 3 files changed, 49 insertions(+), 60 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index c8f8e8152d..30d893c2e5 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1,65 +1,55 @@ -import traceback -from util.json_request import expect_json -import exceptions -import json -import logging -import mimetypes -import os -import StringIO -import sys -import time -import tarfile -import shutil -import tempfile -from datetime import datetime -from collections import defaultdict -from uuid import uuid4 -from lxml import etree -from path import path -from shutil import rmtree - -# to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' +from .utils import get_course_location_for_item, get_lms_link_for_item, \ + compute_unit_state, get_date_display, UnitState from PIL import Image - -from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden -from django.contrib.auth.decorators import login_required -from django.core.exceptions import PermissionDenied -from django.core.context_processors import csrf -from django_future.csrf import ensure_csrf_cookie -from django.core.urlresolvers import reverse +from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, \ + create_all_course_groups, get_user_by_email, add_user_to_course_group, \ + remove_user_from_course_group, is_user_in_course_group_role, \ + get_users_in_course_group_by_role +from cache_toolbox.core import del_cached_content +from collections import defaultdict +from datetime import datetime from django.conf import settings -from django import forms -from django.shortcuts import redirect - -from xmodule.modulestore import Location -from xmodule.modulestore.exceptions import ItemNotFoundError -from xmodule.x_module import ModuleSystem +from django.contrib.auth.decorators import login_required +from django.core.context_processors import csrf +from django.core.exceptions import PermissionDenied +from django.core.urlresolvers import reverse +from django.http import HttpResponse, Http404, HttpResponseBadRequest, \ + HttpResponseForbidden +from django_future.csrf import ensure_csrf_cookie +from external_auth.views import ssl_login_shortcut +from functools import partial +from mitxmako.shortcuts import render_to_response, render_to_string +from path import path +from static_replace import replace_urls +from util.json_request import expect_json +from uuid import uuid4 +from xmodule.contentstore.content import StaticContent +from xmodule.contentstore.django import contentstore from xmodule.error_module import ErrorDescriptor from xmodule.errortracker import exc_info_to_str -from static_replace import replace_urls -from external_auth.views import ssl_login_shortcut - -from mitxmako.shortcuts import render_to_response, render_to_string -from xmodule.modulestore.django import modulestore -from xmodule_modifiers import replace_static_urls, wrap_xmodule from xmodule.exceptions import NotFoundError -from xmodule.timeparse import parse_time, stringify_time -from functools import partial -from itertools import groupby -from operator import attrgetter - -from xmodule.contentstore.django import contentstore -from xmodule.contentstore.content import StaticContent - -from cache_toolbox.core import set_cached_content, get_cached_content, del_cached_content -from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role -from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group -from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, create_all_course_groups -from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display, UnitState - -from xmodule.templates import all_templates +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.xml_importer import import_from_xml -from xmodule.modulestore.xml import edx_xml_parser +from xmodule.timeparse import stringify_time +from xmodule.x_module import ModuleSystem +from xmodule_modifiers import replace_static_urls, wrap_xmodule +import json +import logging +import os +import shutil +import sys +import tarfile +import time + +# to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' + + + + + + log = logging.getLogger(__name__) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 5bccf77a1d..e6242ace56 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -19,11 +19,7 @@ EMAIL_BACKEND = 'django_ses.SESBackend' SESSION_ENGINE = 'django.contrib.sessions.backends.cache' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' -<<<<<<< HEAD -# Disable askbot, enable Berkeley forums -======= # Enable Berkeley forums ->>>>>>> origin/master MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True # IMPORTANT: With this enabled, the server must always be behind a proxy that diff --git a/lms/envs/common.py b/lms/envs/common.py index 85918c7c33..9654031e95 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -359,7 +359,10 @@ MIDDLEWARE_CLASSES = ( 'course_wiki.course_nav.Middleware', +<<<<<<< HEAD 'django.middleware.transaction.TransactionMiddleware', +======= +>>>>>>> remove (almost) all references to askbot. # 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django_comment_client.utils.ViewNameMiddleware', From 956b70478610a6f20a1484436b55d136e425593e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 30 Oct 2012 19:54:15 -0400 Subject: [PATCH 028/243] add migration to remove askbot columns --- common/djangoapps/student/migrations/0021_remove_askbot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/djangoapps/student/migrations/0021_remove_askbot.py b/common/djangoapps/student/migrations/0021_remove_askbot.py index 89f7208f40..b23882cd5f 100644 --- a/common/djangoapps/student/migrations/0021_remove_askbot.py +++ b/common/djangoapps/student/migrations/0021_remove_askbot.py @@ -102,6 +102,7 @@ class Migration(SchemaMigration): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) }, +<<<<<<< HEAD 'student.testcenteruser': { 'Meta': {'object_name': 'TestCenterUser'}, 'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}), @@ -130,6 +131,8 @@ class Migration(SchemaMigration): 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'unique': 'True'}), 'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}) }, +======= +>>>>>>> add migration to remove askbot columns 'student.userprofile': { 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), From 60368ec37024e2ee2fff2930ce5fe3cbfcebf4dc Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 30 Oct 2012 20:49:10 -0400 Subject: [PATCH 029/243] Revert overly aggressive killing. (don't worry--askbot is still dead) --- lms/envs/common.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index 9654031e95..85918c7c33 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -359,10 +359,7 @@ MIDDLEWARE_CLASSES = ( 'course_wiki.course_nav.Middleware', -<<<<<<< HEAD 'django.middleware.transaction.TransactionMiddleware', -======= ->>>>>>> remove (almost) all references to askbot. # 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django_comment_client.utils.ViewNameMiddleware', From e56030ef18be07504f79780e5866911e7529f356 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Mon, 12 Nov 2012 23:14:20 -0500 Subject: [PATCH 030/243] update press page mentions through September, and home page pointers for early November. --- lms/templates/press.json | 1 - 1 file changed, 1 deletion(-) diff --git a/lms/templates/press.json b/lms/templates/press.json index 24e4028bc7..9cc79783ae 100644 --- a/lms/templates/press.json +++ b/lms/templates/press.json @@ -1,5 +1,4 @@ [ - { "title": "The Year of the MOOC", "url": "http://www.nytimes.com/2012/11/04/education/edlife/massive-open-online-courses-are-multiplying-at-a-rapid-pace.html", From cb36d675e3ba1b9e33dd2b1c3caa344d8df1e547 Mon Sep 17 00:00:00 2001 From: Brian Wilson Date: Wed, 14 Nov 2012 11:55:08 -0500 Subject: [PATCH 031/243] add press mentions for October, update missing logos, and add sorting. --- lms/templates/press.json | 424 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) diff --git a/lms/templates/press.json b/lms/templates/press.json index 9cc79783ae..b165037544 100644 --- a/lms/templates/press.json +++ b/lms/templates/press.json @@ -1,4 +1,428 @@ [ + + { + "title": "The Year of the MOOC", + "url": "http://www.nytimes.com/2012/11/04/education/edlife/massive-open-online-courses-are-multiplying-at-a-rapid-pace.html", + "author": "Laura Pappano", + "image": "nyt_logo_178x138.jpeg", + "deck": null, + "publication": "The New York Times", + "publish_date": "November 2, 2012" + }, + { + "title": "The Most Important Education Technology in 200 Years", + "url": "http://www.technologyreview.com/news/506351/the-most-important-education-technology-in-200-years/", + "author": "Antonio Regalado", + "image": "techreview_logo_178x138.jpg", + "deck": null, + "publication": "Technology Review", + "publish_date": "November 2, 2012" + }, + { + "title": "Classroom in the Cloud", + "url": "http://harvardmagazine.com/2012/11/classroom-in-the-cloud", + "author": null, + "image": "harvardmagazine_logo_178x138.jpeg", + "deck": null, + "publication": "Harvard Magazine", + "publish_date": "November-December 2012" + }, + { + "title": "How do you stop online students cheating?", + "url": "http://www.bbc.co.uk/news/business-19661899", + "author": "Sean Coughlan", + "image": "bbc_logo_178x138.jpeg", + "deck": null, + "publication": "BBC", + "publish_date": "October 31, 2012" + }, + { + "title": "VMware to provide software for HarvardX CS50x", + "url": "http://tech.mit.edu/V132/N48/edxvmware.html", + "author": "Stan Gill", + "image": "thetech_logo_178x138.jpg", + "deck": null, + "publication": "The Tech", + "publish_date": "October 26, 2012" + }, + { + "title": "EdX platform integrates into classes", + "url": "http://tech.mit.edu/V132/N48/801edx.html", + "author": "Leon Lin", + "image": "thetech_logo_178x138.jpg", + "deck": null, + "publication": "The Tech", + "publish_date": "October 26, 2012" + }, + { + "title": "VMware Offers Free Software to edX Learners", + "url": "http://campustechnology.com/articles/2012/10/25/vmware-offers-free-virtualization-software-for-edx-computer-science-students.aspx", + "author": "Joshua Bolkan", + "image": "campustech_logo_178x138.jpg", + "deck": "VMware Offers Free Virtualization Software for EdX Computer Science Students", + "publication": "Campus Technology", + "publish_date": "October 25, 2012" + }, + { + "title": "Lone Star moots charges to make Moocs add up", + "url": "http://www.timeshighereducation.co.uk/story.asp?sectioncode=26&storycode=421577&c=1", + "author": "David Matthews", + "image": "timeshighered_logo_178x138.jpg", + "deck": null, + "publication": "Times Higher Education", + "publish_date": "October 25, 2012" + }, + { + "title": "Free, high-quality and with mass appeal", + "url": "http://www.ft.com/intl/cms/s/2/73030f44-d4dd-11e1-9444-00144feabdc0.html#axzz2A9qvk48A", + "author": "Rebecca Knight", + "image": "ft_logo_178x138.jpg", + "deck": null, + "publication": "Financial Times", + "publish_date": "October 22, 2012" + }, + { + "title": "Getting the most out of an online education", + "url": "http://www.reuters.com/article/2012/10/19/us-education-courses-online-idUSBRE89I17120121019", + "author": "Kathleen Kingsbury", + "image": "reuters_logo_178x138.jpg", + "deck": null, + "publication": "Reuters", + "publish_date": "October 19, 2012" + }, + { + "title": "EdX announces partnership with Cengage", + "url": "http://tech.mit.edu/V132/N46/cengage.html", + "author": "Leon Lin", + "image": "thetech_logo_178x138.jpg", + "deck": null, + "publication": "The Tech", + "publish_date": "October 19, 2012" + }, + { + "title": "U Texas System Joins EdX", + "url": "http://campustechnology.com/articles/2012/10/18/u-texas-system-joins-edx.aspx", + "author": "Joshua Bolkan", + "image": "campustech_logo_178x138.jpg", + "deck": null, + "publication": "Campus Technology", + "publish_date": "October 18, 2012" + }, + { + "title": "San Jose State University Runs Blended Learning Course Using edX ", + "url": "http://chronicle.com/blogs/wiredcampus/san-jose-state-u-says-replacing-live-lectures-with-videos-increased-test-scores/40470", + "author": "Alisha Azevedo", + "image": "chroniclehighered_logo_178x138.jpeg", + "deck": "San Jose State U. Says Replacing Live Lectures With Videos Increased Test Scores", + "publication": "Chronicle of Higher Education", + "publish_date": "October 17, 2012" + }, + { + "title": "Online university to charge tuition fees", + "url": "http://www.bbc.co.uk/news/education-19964787", + "author": "Sean Coughlan", + "image": "bbc_logo_178x138.jpeg", + "deck": null, + "publication": "BBC", + "publish_date": "October 17, 2012" + }, + { + "title": "HarvardX marks the spot", + "url": "http://news.harvard.edu/gazette/story/2012/10/harvardx-marks-the-spot/", + "author": "Tania delLuzuriaga", + "image": "harvardgazette_logo_178x138.jpeg", + "deck": null, + "publication": "Harvard Gazette", + "publish_date": "October 17, 2012" + }, + { + "title": "Harvard EdX Enrolls Near 100000 Students for Free Online Classes", + "url": "http://www.collegeclasses.com/harvard-edx-enrolls-near-100000-students-for-free-online-classes/", + "author": "Keith Koong", + "image": "college_classes_logo_178x138.jpg", + "deck": null, + "publication": "CollegeClasses.com", + "publish_date": "October 17, 2012" + }, + { + "title": "Cengage Learning to Provide Book Content and Pedagogy through edX's Not-for-Profit Interactive Study Via the Web", + "url": "http://www.outsellinc.com/our_industry/headlines/1087978", + "author": null, + "image": "outsell_logo_178x138.jpg", + "deck": null, + "publication": "Outsell.com", + "publish_date": "October 17, 2012" + }, + { + "title": "University of Texas System Embraces MOOCs", + "url": "http://www.usnewsuniversitydirectory.com/articles/university-of-texas-system-embraces-moocs_12713.aspx#.UIBLVq7bNzo", + "author": "Chris Hassan", + "image": "usnews_logo_178x138.jpeg", + "deck": null, + "publication": "US News", + "publish_date": "October 17, 2012" + }, + { + "title": "Texas MOOCs for Credit?", + "url": "http://www.insidehighered.com/news/2012/10/16/u-texas-aims-use-moocs-reduce-costs-increase-completion", + "author": "Steve Kolowich", + "image": "insidehighered_logo_178x138.jpg", + "deck": null, + "publication": "Insider Higher Ed", + "publish_date": "October 16, 2012" + }, + { + "title": "University of Texas Joins Harvard-Founded edX", + "url": "http://www.thecrimson.com/article/2012/10/16/University-of-Texas-edX/", + "author": "Kevin J. Wu", + "image": "harvardcrimson_logo_178x138.jpeg", + "deck": null, + "publication": "The Crimson", + "publish_date": "October 16, 2012" + }, + { + "title": "Entire UT System to join edX", + "url": "http://tech.mit.edu/V132/N45/edx.html", + "author": "Ethan A. Solomon", + "image": "thetech_logo_178x138.jpg", + "deck": null, + "publication": "The Tech", + "publish_date": "October 16, 2012" + }, + { + "title": "First University System Joins edX Platform", + "url": "http://www.govtech.com/education/First-University-System-Joins-edX-platform.html", + "author": "Tanya Roscoria", + "image": "govtech_logo_178x138.jpg", + "deck": null, + "publication": "GovTech.com", + "publish_date": "October 16, 2012" + }, + { + "title": "University of Texas Joining Harvard, MIT Online Venture", + "url": "http://www.bloomberg.com/news/2012-10-15/university-of-texas-joining-harvard-mit-online-venture.html", + "author": "David Mildenberg", + "image": "bloomberg_logo_178x138.jpeg", + "deck": null, + "publication": "Bloomberg", + "publish_date": "October 15, 2012" + }, + { + "title": "University of Texas Joining Harvard, MIT Online Venture", + "url": "http://www.businessweek.com/news/2012-10-15/university-of-texas-joining-harvard-mit-online-venture", + "author": "David Mildenberg", + "image": "busweek_logo_178x138.jpg", + "deck": null, + "publication": "Business Week", + "publish_date": "October 15, 2012" + }, + { + "title": "Univ. of Texas joins online course program edX", + "url": "http://news.yahoo.com/univ-texas-joins-online-course-program-edx-172202035--finance.html", + "author": "Chris Tomlinson", + "image": "ap_logo_178x138.jpg", + "deck": null, + "publication": "Associated Press", + "publish_date": "October 15, 2012" + }, + { + "title": "U. of Texas Plans to Join edX", + "url": "http://www.insidehighered.com/quicktakes/2012/10/15/u-texas-plans-join-edx", + "author": null, + "image": "insidehighered_logo_178x138.jpg", + "deck": null, + "publication": "Inside Higher Ed", + "publish_date": "October 15, 2012" + }, + { + "title": "U. of Texas System Is Latest to Sign Up With edX for Online Courses", + "url": "http://chronicle.com/blogs/wiredcampus/u-of-texas-system-is-latest-to-sign-up-with-edx-for-online-courses/40440", + "author": "Alisha Azevedo", + "image": "chroniclehighered_logo_178x138.jpeg", + "deck": null, + "publication": "Chronicle of Higher Education", + "publish_date": "October 15, 2012" + }, + { + "title": "First University System Joins edX", + "url": "http://www.centerdigitaled.com/news/First-University-System-Joins-edX.html", + "author": "Tanya Roscoria", + "image": "center_digeducation_logo_178x138.jpg", + "deck": null, + "publication": "Center for Digital Education", + "publish_date": "October 15, 2012" + }, + { + "title": "University of Texas Joins Harvard, MIT in edX Online Learning Venture", + "url": "http://harvardmagazine.com/2012/10/university-of-texas-joins-harvard-mit-edx", + "author": null, + "image": "harvardmagazine_logo_178x138.jpeg", + "deck": null, + "publication": "Harvard Magazine", + "publish_date": "October 15, 2012" + }, + { + "title": "University of Texas joins edX", + "url": "http://www.masshightech.com/stories/2012/10/15/daily13-University-of-Texas-joins-edX.html", + "author": "Don Seiffert", + "image": "masshightech_logo_178x138.jpg", + "deck": null, + "publication": "MassHighTech", + "publish_date": "October 15, 2012" + }, + { + "title": "UT System to Forge Partnership with EdX", + "url": "http://www.texastribune.org/texas-education/higher-education/ut-system-announce-partnership-edx/", + "author": "Reeve Hamilton", + "image": "texastribune_logo_178x138.jpg", + "deck": null, + "publication": "Texas Tribune", + "publish_date": "October 15, 2012" + }, + { + "title": "UT System puts $5 million into online learning initiative", + "url": "http://www.statesman.com/news/news/local/ut-system-puts-5-million-into-online-learning-init/nSdd5/", + "author": "Ralph K.M. Haurwitz", + "image": "austin_statesman_logo_178x138.jpg", + "deck": null, + "publication": "The Austin Statesman", + "publish_date": "October 15, 2012" + }, + { + "title": "Harvard’s Online Classes Sound Pretty Popular", + "url": "http://blogs.bostonmagazine.com/boston_daily/2012/10/15/harvards-online-classes-sound-pretty-popular/", + "author": "Eric Randall", + "image": "bostonmag_logo_178x138.jpg", + "deck": null, + "publication": "Boston Magazine", + "publish_date": "October 15, 2012" + }, + { + "title": "Harvard Debuts Free Online Courses", + "url": "http://www.ibtimes.com/harvard-debuts-free-online-courses-846629", + "author": "Eli Epstein", + "image": "ibtimes_logo_178x138.jpg", + "deck": null, + "publication": "International Business Times", + "publish_date": "October 15, 2012" + }, + { + "title": "UT System Joins Online Learning Effort", + "url": "http://www.texastechpulse.com/ut_system_joins_online_learning_effort/s-0045632.html", + "author": null, + "image": "texaspulse_logo_178x138.jpg", + "deck": null, + "publication": "Texas Tech Pulse", + "publish_date": "October 15, 2012" + }, + { + "title": "University of Texas Joins edX", + "url": "http://www.onlinecolleges.net/2012/10/15/university-of-texas-joins-edx/", + "author": "Alex Wukman", + "image": "online_colleges_logo_178x138.jpg", + "deck": null, + "publication": "Online Colleges.net", + "publish_date": "October 15, 2012" + }, + { + "title": "100,000 sign up for first Harvard online courses", + "url": "http://www.masslive.com/news/index.ssf/2012/10/100000_sign_up_for_first_harva.html", + "author": null, + "image": "ap_logo_178x138.jpg", + "deck": null, + "publication": "Associated Press", + "publish_date": "October 15, 2012" + }, + { + "title": "In the new Listener, on sale from 14.10.12", + "url": "http://www.listener.co.nz/commentary/the-internaut/in-the-new-listener-on-sale-from-14-10-12/", + "author": null, + "image": "nz_listener_logo_178x138.jpg", + "deck": null, + "publication": "The Listener", + "publish_date": "October 14, 2012" + }, + { + "title": "HarvardX Classes to Begin Tomorrow", + "url": "http://www.thecrimson.com/article/2012/10/14/harvardx-classes-start-tomorrow/", + "author": "Hana N. Rouse", + "image": "harvardcrimson_logo_178x138.jpeg", + "deck": null, + "publication": "The Crimson", + "publish_date": "October 14, 2012" + }, + { + "title": "Online Harvard University courses draw well", + "url": "http://bostonglobe.com/metro/2012/10/14/harvard-launching-free-online-courses-sign-for-first-two-classes/zBDuHY0zqD4OESrXWfEgML/story.html", + "author": "Brock Parker", + "image": "bostonglobe_logo_178x138.jpeg", + "deck": null, + "publication": "Boston Globe", + "publish_date": "October 14, 2012" + }, + { + "title": "Harvard ready to launch its first free online courses Monday", + "url": "http://www.boston.com/yourtown/news/cambridge/2012/10/harvard_ready_to_launch_its_fi.html", + "author": "Brock Parker", + "image": "bostonglobe_logo_178x138.jpeg", + "deck": null, + "publication": "Boston Globe", + "publish_date": "October 12, 2012" + }, + { + "title": "edX: Harvard's New Domain", + "url": "http://www.thecrimson.com/article/2012/10/4/edx-scrutiny-online-learning/ ", + "author": "Delphine Rodrik and Kevin Su", + "image": "harvardcrimson_logo_178x138.jpeg", + "deck": null, + "publication": "The Crimson", + "publish_date": "October 4, 2012" + }, + { + "title": "New Experiments in the edX Higher Ed Petri Dish", + "url": "http://www.nonprofitquarterly.org/policysocial-context/21116-new-experiments-in-the-edx-higher-ed-petri-dish.html", + "author": "Michelle Shumate", + "image": "npq_logo_178x138.jpg", + "deck": null, + "publication": "Non-Profit Quarterly", + "publish_date": "October 4, 2012" + }, + { + "title": "What Campuses Can Learn From Online Teaching", + "url": "http://online.wsj.com/article/SB10000872396390444620104578012262106378182.html?mod=googlenews_wsj", + "author": "Rafael Reif", + "image": "wsj_logo_178x138.jpg", + "deck": null, + "publication": "Wall Street Journal", + "publish_date": "October 2, 2012" + }, + { + "title": "MongoDB courses to be offered via edX", + "url": "http://tech.mit.edu/V132/N42/edxmongodb.html", + "author": "Jake H. Gunter", + "image": "thetech_logo_178x138.jpg", + "deck": null, + "publication": "The Tech", + "publish_date": "October 2, 2012" + }, + { + "title": "5 Ways That edX Could Change Education", + "url": "http://chronicle.com/article/5-Ways-That-edX-Could-Change/134672/", + "author": "Marc Parry", + "image": "chroniclehighered_logo_178x138.jpeg", + "deck": null, + "publication": "Chronicle of Higher Education", + "publish_date": "October 1, 2012" + }, + { + "title": "MIT profs wait to teach you, for free", + "url": "http://www.dnaindia.com/mumbai/report_mit-profs-wait-to-teach-you-for-free_1747273", + "author": "Kanchan Srivastava", + "image": "dailynews_india_logo_178x138.jpg", + "deck": null, + "publication": "Daily News and Analysis India", + "publish_date": "October 1, 2012" + }, { "title": "The Year of the MOOC", "url": "http://www.nytimes.com/2012/11/04/education/edlife/massive-open-online-courses-are-multiplying-at-a-rapid-pace.html", From 336caa0feb48a08b8a840809e5b58566bf2c470b Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 15 Nov 2012 14:55:21 -0500 Subject: [PATCH 032/243] Batch together alter statements to minimize how long we lock the auth_users table --- .../migrations/0020_prep_cleanup_askbot.py | 163 ------------------ .../student/migrations/0021_remove_askbot.py | 3 - 2 files changed, 166 deletions(-) delete mode 100644 common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py diff --git a/common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py b/common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py deleted file mode 100644 index 14cdf89d96..0000000000 --- a/common/djangoapps/student/migrations/0020_prep_cleanup_askbot.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import DataMigration -from django.db import models -import datetime - -ASKBOT_AUTH_USER_COLUMNS = ( - 'website', - 'about', - 'gold', - 'email_isvalid', - 'real_name', - 'location', - 'reputation', - 'gravatar', - 'bronze', - 'last_seen', - 'silver', - 'questions_per_page', - 'new_response_count', - 'seen_response_count', -) - -class Migration(DataMigration): - def make_column_nullable(self, table, column): - field, args, kwargs = self.models[table][column] - if kwargs.get('default') == 'datetime.datetime.now': - kwargs['default'] = datetime.datetime.now - kwargs['null'] = True - db.alter_column(table.replace('.', '_'), column, self.gf(field)(*args, **kwargs)) - - def reset_column(self, table, column): - field, args, kwargs = self.models[table][column] - if kwargs.get('default') == 'datetime.datetime.now': - kwargs['default'] = datetime.datetime.now - db.alter_column(table.replace('.', '_'), column, self.gf(field)(*args, **kwargs)) - - def forwards(self, orm): - "Write your forwards methods here." - for column in ASKBOT_AUTH_USER_COLUMNS: - self.make_column_nullable('auth.user', column) - - def backwards(self, orm): - "Write your backwards methods here." - for column in ASKBOT_AUTH_USER_COLUMNS: - self.reset_column('auth.user', column) - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), - 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), - 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), - 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), - 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), - 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), - 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), - 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), - 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), - 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'student.courseenrollment': { - 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, - 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'student.pendingemailchange': { - 'Meta': {'object_name': 'PendingEmailChange'}, - 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) - }, - 'student.pendingnamechange': { - 'Meta': {'object_name': 'PendingNameChange'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) - }, - 'student.registration': { - 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, - 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) - }, - 'student.userprofile': { - 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, - 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), - 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), - 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), - 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), - 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), - 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) - }, - 'student.usertestgroup': { - 'Meta': {'object_name': 'UserTestGroup'}, - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), - 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) - } - } - - complete_apps = ['student'] - symmetrical = True diff --git a/common/djangoapps/student/migrations/0021_remove_askbot.py b/common/djangoapps/student/migrations/0021_remove_askbot.py index b23882cd5f..89f7208f40 100644 --- a/common/djangoapps/student/migrations/0021_remove_askbot.py +++ b/common/djangoapps/student/migrations/0021_remove_askbot.py @@ -102,7 +102,6 @@ class Migration(SchemaMigration): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) }, -<<<<<<< HEAD 'student.testcenteruser': { 'Meta': {'object_name': 'TestCenterUser'}, 'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}), @@ -131,8 +130,6 @@ class Migration(SchemaMigration): 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'unique': 'True'}), 'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}) }, -======= ->>>>>>> add migration to remove askbot columns 'student.userprofile': { 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), From 91a91f4ddbe73fd8c0267d158b137d2ef82e0940 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Thu, 8 Nov 2012 16:53:01 -0500 Subject: [PATCH 033/243] Hopefully the course-info changes I had made w/o link destruction --- cms/djangoapps/contentstore/utils.py | 25 ++++++++ cms/djangoapps/contentstore/views.py | 62 +++++++++++++++++- .../coffee/src/views/course_info_edit.coffee | 63 +++++++++++++++++++ cms/templates/course_info.html | 33 ++++++++++ cms/templates/widgets/header.html | 1 + cms/urls.py | 7 ++- 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 cms/static/coffee/src/views/course_info_edit.coffee create mode 100644 cms/templates/course_info.html diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 18afd331d0..508236a1e9 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -33,6 +33,31 @@ def get_course_location_for_item(location): return location +def get_course_for_item(location): + ''' + cdodge: for a given Xmodule, return the course that it belongs to + NOTE: This makes a lot of assumptions about the format of the course location + Also we have to assert that this module maps to only one course item - it'll throw an + assert if not + ''' + item_loc = Location(location) + + # @hack! We need to find the course location however, we don't + # know the 'name' parameter in this context, so we have + # to assume there's only one item in this query even though we are not specifying a name + course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None] + courses = modulestore().get_items(course_search_location) + + # make sure we found exactly one match on this above course search + found_cnt = len(courses) + if found_cnt == 0: + raise BaseException('Could not find course at {0}'.format(course_search_location)) + + if found_cnt > 1: + raise BaseException('Found more than one course at {0}. There should only be one!!! Dump = {1}'.format(course_search_location, courses)) + + return courses[0] + def get_lms_link_for_item(location, preview=False): location = Location(location) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 30d893c2e5..e3856e40ab 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -42,6 +42,8 @@ import shutil import sys import tarfile import time +from contentstore.course_info_model import get_course_updates,\ + update_course_updates, delete_course_update # to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' @@ -336,7 +338,7 @@ def edit_unit(request, location): def preview_component(request, location): # TODO (vshnayder): change name from id to location in coffee+html as well. if not has_access(request.user, location): - raise Http404 # TODO (vshnayder): better error + raise HttpResponseForbidden() component = modulestore().get_item(location) @@ -869,6 +871,9 @@ def edit_static(request, org, course, coursename): return render_to_response('edit-static-page.html', {}) +def settings(request, org, course, coursename): + return render_to_response('settings.html', {}) + def edit_tabs(request, org, course, coursename): location = ['i4x', org, course, 'course', coursename] course_item = modulestore().get_item(location) @@ -896,6 +901,61 @@ def server_error(request): return render_to_response('error.html', {'error': '500'}) +@login_required +@ensure_csrf_cookie +def course_info(request, org, course, name, provided_id=None): + """ + Send models and views as well as html for editing the course info to the client. + + org, course, name: Attributes of the Location for the item to edit + """ + location = ['i4x', org, course, 'course', name] + + # check that logged in user has permissions to this item + if not has_access(request.user, location): + raise PermissionDenied() + + course_module = modulestore().get_item(location) + + # get current updates + location = ['i4x', org, course, 'course_info', "updates"] + + return render_to_response('course_info.html', { + 'active_tab': 'courseinfo-tab', + 'context_course': course_module, + 'url_base' : "/" + org + "/" + course + "/", + 'course_updates' : json.dumps(get_course_updates(location)) + }) + +@expect_json +@login_required +@ensure_csrf_cookie +def course_info_updates(request, org, course, provided_id=None): + """ + restful CRUD operations on course_info updates. + + org, course: Attributes of the Location for the item to edit + provided_id should be none if it's new (create) and a composite of the update db id + index otherwise. + """ + # ??? No way to check for access permission afaik + # get current updates + location = ['i4x', org, course, 'course_info', "updates"] + # NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! + if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: + real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'] + else: + real_method = request.method + + if request.method == 'GET': + return HttpResponse(json.dumps(get_course_updates(location)), mimetype="application/json") + elif real_method == 'POST': + # new instance (unless django makes PUT a POST): updates are coming as POST. Not sure why. + return HttpResponse(json.dumps(update_course_updates(location, request.POST, provided_id)), mimetype="application/json") + elif real_method == 'PUT': + return HttpResponse(json.dumps(update_course_updates(location, request.POST, provided_id)), mimetype="application/json") + elif real_method == 'DELETE': # coming as POST need to pull from Request Header X-HTTP-Method-Override DELETE + return HttpResponse(json.dumps(delete_course_update(location, request.POST, provided_id)), mimetype="application/json") + @login_required @ensure_csrf_cookie def asset_index(request, org, course, name): diff --git a/cms/static/coffee/src/views/course_info_edit.coffee b/cms/static/coffee/src/views/course_info_edit.coffee new file mode 100644 index 0000000000..49cb90c47d --- /dev/null +++ b/cms/static/coffee/src/views/course_info_edit.coffee @@ -0,0 +1,63 @@ +## Derived from and should inherit from a common ancestor w/ ModuleEdit +class CMS.Views.CourseInfoEdit extends Backbone.View + tagName: 'div' + className: 'component' + + events: + "click .component-editor .cancel-button": 'clickCancelButton' + "click .component-editor .save-button": 'clickSaveButton' + "click .component-actions .edit-button": 'clickEditButton' + "click .component-actions .delete-button": 'onDelete' + + initialize: -> + @render() + + $component_editor: => @$el.find('.component-editor') + + loadDisplay: -> + XModule.loadModule(@$el.find('.xmodule_display')) + + loadEdit: -> + if not @module + @module = XModule.loadModule(@$el.find('.xmodule_edit')) + + # don't show metadata (deprecated for course_info) + render: -> + if @model.id + @$el.load("/preview_component/#{@model.id}", => + @loadDisplay() + @delegateEvents() + ) + + clickSaveButton: (event) => + event.preventDefault() + data = @module.save() + @model.save(data).done( => + # # showToastMessage("Your changes have been saved.", null, 3) + @module = null + @render() + @$el.removeClass('editing') + ).fail( -> + showToastMessage("There was an error saving your changes. Please try again.", null, 3) + ) + + clickCancelButton: (event) -> + event.preventDefault() + @$el.removeClass('editing') + @$component_editor().slideUp(150) + + clickEditButton: (event) -> + event.preventDefault() + @$el.addClass('editing') + @$component_editor().slideDown(150) + @loadEdit() + + onDelete: (event) -> + # clear contents, don't delete + @model.definition.data = "
                  " + # TODO change label to 'clear' + + onNew: (event) -> + ele = $(@model.definition.data).find("ol") + if (ele) + ele = $(ele).first().prepend("
                1. " + $.datepicker.formatDate('MM d', new Date()) + "

                  /n
                2. "); \ No newline at end of file diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html new file mode 100644 index 0000000000..72facf7c2e --- /dev/null +++ b/cms/templates/course_info.html @@ -0,0 +1,33 @@ +<%inherit file="base.html" /> + +<%block name="title">Course Info + +<%block name="jsextra"> + + + +<%block name="content"> +
                  +
                  +

                  Course Info

                  +
                  +
                  +

                  Updates

                  + New Update +
                  + +
                  +
                  + +
                  +
                  + \ No newline at end of file diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 877f03533c..73ce3f0604 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -10,6 +10,7 @@ ${context_course.display_name}
                  • Courseware
                  • +
                  • Course Info
                  • Tabs
                  • Assets
                  • Users
                  • diff --git a/cms/urls.py b/cms/urls.py index e0dbc68129..4c2f2b361e 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -34,7 +34,12 @@ urlpatterns = ('', 'contentstore.views.remove_user', name='remove_user'), url(r'^(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)/remove_user$', 'contentstore.views.remove_user', name='remove_user'), - url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', name='static_pages'), + url(r'^(?P[^/]+)/(?P[^/]+)/info/(?P[^/]+)$', 'contentstore.views.course_info', name='course_info'), + # ??? Is the following necessary or will the one below work w/ id=None if not sent? + # url(r'^(?P[^/]+)/(?P[^/]+)/course_info/updates$', 'contentstore.views.course_info_updates', name='course_info'), + url(r'^(?P[^/]+)/(?P[^/]+)/course_info/updates/(?P.*)$', 'contentstore.views.course_info_updates', name='course_info'), + url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', + name='static_pages'), url(r'^edit_static/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_static', name='edit_static'), url(r'^edit_tabs/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_tabs', name='edit_tabs'), url(r'^(?P[^/]+)/(?P[^/]+)/assets/(?P[^/]+)$', 'contentstore.views.asset_index', name='asset_index'), From 88cc5a080bf9e2a37d85bf93d2f7fc04942e3406 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Fri, 16 Nov 2012 12:10:39 -0500 Subject: [PATCH 034/243] Everything tested and ready for Tom to fix --- .../contentstore/course_info_model.py | 148 ++++++++++++++++++ cms/static/coffee/files.json | 2 +- .../client_templates/course_info_update.html | 28 ++++ .../src/client_templates/load_templates.html | 14 ++ .../coffee/src/views/course_info_edit.coffee | 63 -------- cms/static/js/models/course_info.js | 34 ++++ cms/static/js/template_loader.js | 77 +++++++++ cms/static/js/views/course_info_edit.js | 138 ++++++++++++++++ cms/templates/course_info.html | 33 ++-- .../xmodule/templates/courseinfo/empty.yaml | 2 +- 10 files changed, 465 insertions(+), 74 deletions(-) create mode 100644 cms/djangoapps/contentstore/course_info_model.py create mode 100644 cms/static/coffee/src/client_templates/course_info_update.html create mode 100644 cms/static/coffee/src/client_templates/load_templates.html delete mode 100644 cms/static/coffee/src/views/course_info_edit.coffee create mode 100644 cms/static/js/models/course_info.js create mode 100644 cms/static/js/template_loader.js create mode 100644 cms/static/js/views/course_info_edit.js diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py new file mode 100644 index 0000000000..87dfa5da8f --- /dev/null +++ b/cms/djangoapps/contentstore/course_info_model.py @@ -0,0 +1,148 @@ +from xmodule.modulestore.exceptions import ItemNotFoundError +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from lxml import etree +import re +from django.http import HttpResponseBadRequest + +## TODO store as array of { date, content } and override course_info_module.definition_from_xml +## This should be in a class which inherits from XmlDescriptor +def get_course_updates(location): + """ + Retrieve the relevant course_info updates and unpack into the model which the client expects: + [{id : location.url() + idx to make unique, date : string, content : html string}] + """ + try: + course_updates = modulestore('direct').get_item(location) + except ItemNotFoundError: + template = Location(['i4x', 'edx', "templates", 'course_info', "Empty"]) + course_updates = modulestore('direct').clone_item(template, Location(location)) + + # current db rep: {"_id" : locationjson, "definition" : { "data" : "
                      [
                    1. date

                      content
                    2. ]
                    "} "metadata" : ignored} + location_base = course_updates.location.url() + + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. + try: + course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + course_html_parsed = etree.fromstring("
                      ") + + # Confirm that root is
                        , iterate over
                      1. , pull out

                        subs and then rest of val + course_upd_collection = [] + if course_html_parsed.tag == 'ol': + # 0 is the oldest so that new ones get unique idx + for idx, update in enumerate(course_html_parsed.iter("li")): + if (len(update) == 0): + continue + elif (len(update) == 1): + content = update.find("h2").tail + else: + content = etree.tostring(update[1]) + + course_upd_collection.append({"id" : location_base + "/" + str(idx), + "date" : update.findtext("h2"), + "content" : content}) + # return newest to oldest + course_upd_collection.reverse() + return course_upd_collection + +def update_course_updates(location, update, passed_id=None): + """ + Either add or update the given course update. It will add it if the passed_id is absent or None. It will update it if + it has an passed_id which has a valid value. Until updates have distinct values, the passed_id is the location url + an index + into the html structure. + """ + try: + course_updates = modulestore('direct').get_item(location) + except ItemNotFoundError: + return HttpResponseBadRequest + + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. + try: + course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + course_html_parsed = etree.fromstring("
                          ") + + try: + new_html_parsed = etree.fromstring(update['content'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + new_html_parsed = None + + # Confirm that root is
                            , iterate over
                          1. , pull out

                            subs and then rest of val + if course_html_parsed.tag == 'ol': + # ??? Should this use the id in the json or in the url or does it matter? + if passed_id: + element = course_html_parsed.findall("li")[get_idx(passed_id)] + element[0].text = update['date'] + if (len(element) == 1): + if new_html_parsed is not None: + element[0].tail = None + element.append(new_html_parsed) + else: + element[0].tail = update['content'] + else: + if new_html_parsed is not None: + element[1] = new_html_parsed + else: + element.pop(1) + element[0].tail = update['content'] + else: + idx = len(course_html_parsed.findall("li")) + passed_id = course_updates.location.url() + "/" + str(idx) + element = etree.SubElement(course_html_parsed, "li") + date_element = etree.SubElement(element, "h2") + date_element.text = update['date'] + if new_html_parsed is not None: + element[1] = new_html_parsed + else: + date_element.tail = update['content'] + + # update db record + course_updates.definition['data'] = etree.tostring(course_html_parsed) + modulestore('direct').update_item(location, course_updates.definition['data']) + + return {"id" : passed_id, + "date" : update['date'], + "content" :update['content']} + +def delete_course_update(location, update, passed_id): + """ + Delete the given course_info update from the db. + Returns the resulting course_updates b/c their ids change. + """ + if not passed_id: + return HttpResponseBadRequest + + try: + course_updates = modulestore('direct').get_item(location) + except ItemNotFoundError: + return HttpResponseBadRequest + + # TODO use delete_blank_text parser throughout and cache as a static var in a class + # purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break. + try: + course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True)) + except etree.XMLSyntaxError: + course_html_parsed = etree.fromstring("
                              ") + + if course_html_parsed.tag == 'ol': + # ??? Should this use the id in the json or in the url or does it matter? + element_to_delete = course_html_parsed.xpath('/ol/li[position()=' + str(get_idx(passed_id) + 1) + "]") + if element_to_delete: + course_html_parsed.remove(element_to_delete[0]) + + # update db record + course_updates.definition['data'] = etree.tostring(course_html_parsed) + store = modulestore('direct') + store.update_item(location, course_updates.definition['data']) + + return get_course_updates(location) + +def get_idx(passed_id): + """ + From the url w/ idx appended, get the idx. + """ + # TODO compile this regex into a class static and reuse for each call + idx_matcher = re.search(r'.*/(\d)+$', passed_id) + if idx_matcher: + return int(idx_matcher.group(1)) \ No newline at end of file diff --git a/cms/static/coffee/files.json b/cms/static/coffee/files.json index b396bec944..7b2719a047 100644 --- a/cms/static/coffee/files.json +++ b/cms/static/coffee/files.json @@ -3,6 +3,6 @@ "/static/js/vendor/jquery.min.js", "/static/js/vendor/json2.js", "/static/js/vendor/underscore-min.js", - "/static/js/vendor/backbone-min.js" + "/static/js/vendor/backbone.js" ] } diff --git a/cms/static/coffee/src/client_templates/course_info_update.html b/cms/static/coffee/src/client_templates/course_info_update.html new file mode 100644 index 0000000000..54a4a38dde --- /dev/null +++ b/cms/static/coffee/src/client_templates/course_info_update.html @@ -0,0 +1,28 @@ +
                            1. + +
                              +
                              + + + +
                              +
                              + +
                              +
                              + + Save + Cancel +
                              +
                              +

                              + <%= + updateModel.get('date') %> +

                              +
                              <%= updateModel.get('content') %>
                              +
                              + Edit + Delete +
                              +
                            2. \ No newline at end of file diff --git a/cms/static/coffee/src/client_templates/load_templates.html b/cms/static/coffee/src/client_templates/load_templates.html new file mode 100644 index 0000000000..3ff88d6fe5 --- /dev/null +++ b/cms/static/coffee/src/client_templates/load_templates.html @@ -0,0 +1,14 @@ + + +<%block name="jsextra"> + + + + \ No newline at end of file diff --git a/cms/static/coffee/src/views/course_info_edit.coffee b/cms/static/coffee/src/views/course_info_edit.coffee deleted file mode 100644 index 49cb90c47d..0000000000 --- a/cms/static/coffee/src/views/course_info_edit.coffee +++ /dev/null @@ -1,63 +0,0 @@ -## Derived from and should inherit from a common ancestor w/ ModuleEdit -class CMS.Views.CourseInfoEdit extends Backbone.View - tagName: 'div' - className: 'component' - - events: - "click .component-editor .cancel-button": 'clickCancelButton' - "click .component-editor .save-button": 'clickSaveButton' - "click .component-actions .edit-button": 'clickEditButton' - "click .component-actions .delete-button": 'onDelete' - - initialize: -> - @render() - - $component_editor: => @$el.find('.component-editor') - - loadDisplay: -> - XModule.loadModule(@$el.find('.xmodule_display')) - - loadEdit: -> - if not @module - @module = XModule.loadModule(@$el.find('.xmodule_edit')) - - # don't show metadata (deprecated for course_info) - render: -> - if @model.id - @$el.load("/preview_component/#{@model.id}", => - @loadDisplay() - @delegateEvents() - ) - - clickSaveButton: (event) => - event.preventDefault() - data = @module.save() - @model.save(data).done( => - # # showToastMessage("Your changes have been saved.", null, 3) - @module = null - @render() - @$el.removeClass('editing') - ).fail( -> - showToastMessage("There was an error saving your changes. Please try again.", null, 3) - ) - - clickCancelButton: (event) -> - event.preventDefault() - @$el.removeClass('editing') - @$component_editor().slideUp(150) - - clickEditButton: (event) -> - event.preventDefault() - @$el.addClass('editing') - @$component_editor().slideDown(150) - @loadEdit() - - onDelete: (event) -> - # clear contents, don't delete - @model.definition.data = "
                                " - # TODO change label to 'clear' - - onNew: (event) -> - ele = $(@model.definition.data).find("ol") - if (ele) - ele = $(ele).first().prepend("
                              1. " + $.datepicker.formatDate('MM d', new Date()) + "

                                /n
                              2. "); \ No newline at end of file diff --git a/cms/static/js/models/course_info.js b/cms/static/js/models/course_info.js new file mode 100644 index 0000000000..b1d10c8d16 --- /dev/null +++ b/cms/static/js/models/course_info.js @@ -0,0 +1,34 @@ +// single per course holds the updates and handouts +CMS.Models.CourseInfo = Backbone.Model.extend({ + // This model class is not suited for restful operations and is considered just a server side initialized container + url: '', + + defaults: { + "courseId": "", // the location url + "updates" : null, // UpdateCollection + "handouts": null // HandoutCollection + }, + + idAttribute : "courseId" +}); + +// course update -- biggest kludge here is the lack of a real id to map updates to originals +CMS.Models.CourseUpdate = Backbone.Model.extend({ + defaults: { + "date" : $.datepicker.formatDate('MM d', new Date()), + "content" : "" + } +}); + +/* + The intitializer of this collection must set id to the update's location.url and courseLocation to the course's location. Must pass the + collection of updates as [{ date : "month day", content : "html"}] +*/ +CMS.Models.CourseUpdateCollection = Backbone.Collection.extend({ + url : function() {return this.urlbase + "course_info/updates/";}, + + model : CMS.Models.CourseUpdate +}); + + + \ No newline at end of file diff --git a/cms/static/js/template_loader.js b/cms/static/js/template_loader.js new file mode 100644 index 0000000000..b266575b7a --- /dev/null +++ b/cms/static/js/template_loader.js @@ -0,0 +1,77 @@ +// +// TODO Figure out how to initialize w/ static views from server (don't call .load but instead inject in django as strings) +// so this only loads the lazily loaded ones. +(function() { + if (typeof window.templateLoader == 'function') return; + + var templateLoader = { + templateVersion: "0.0.3", + templates: {}, + loadRemoteTemplate: function(templateName, filename, callback) { + if (!this.templates[templateName]) { + var self = this; + jQuery.ajax({url : filename, + success : function(data) { + self.addTemplate(templateName, data); + self.saveLocalTemplates(); + callback(data); + }, + error : function(xhdr, textStatus, errorThrown) { + console.log(textStatus); }, + dataType : "html" + }) + } + else { + callback(this.templates[templateName]); + } + }, + + addTemplate: function(templateName, data) { + // is there a reason this doesn't go ahead and compile the template? _.template(data) + // I suppose localstorage use would still req raw string rather than compiled version, but that sd work + // if it maintains a separate cache of uncompiled ones + this.templates[templateName] = data; + }, + + localStorageAvailable: function() { + try { + return 'localStorage' in window && window['localStorage'] !== null; + } catch (e) { + return false; + } + }, + + saveLocalTemplates: function() { + if (this.localStorageAvailable) { + localStorage.setItem("templates", JSON.stringify(this.templates)); + localStorage.setItem("templateVersion", this.templateVersion); + } + }, + + loadLocalTemplates: function() { + if (this.localStorageAvailable) { + var templateVersion = localStorage.getItem("templateVersion"); + if (templateVersion && templateVersion == this.templateVersion) { + var templates = localStorage.getItem("templates"); + if (templates) { + templates = JSON.parse(templates); + for (var x in templates) { + if (!this.templates[x]) { + this.addTemplate(x, templates[x]); + } + } + } + } + else { + localStorage.removeItem("templates"); + localStorage.removeItem("templateVersion"); + } + } + } + + + + }; + templateLoader.loadLocalTemplates(); + window.templateLoader = templateLoader; + })(); diff --git a/cms/static/js/views/course_info_edit.js b/cms/static/js/views/course_info_edit.js new file mode 100644 index 0000000000..4f504d72e8 --- /dev/null +++ b/cms/static/js/views/course_info_edit.js @@ -0,0 +1,138 @@ +/* this view should own everything on the page which has controls effecting its operation + generate other views for the individual editors. + The render here adds views for each update/handout by delegating to their collections but does not + generate any html for the surrounding page. +*/ +CMS.Views.CourseInfoEdit = Backbone.View.extend({ + // takes CMS.Models.CourseInfo as model + tagName: 'div', + + render: function() { + // instantiate the ClassInfoUpdateView and delegate the proper dom to it + new CMS.Views.ClassInfoUpdateView({ + el: this.$('#course-update-view'), + collection: this.model.get('updates') + }); + // TODO instantiate the handouts view + return this; + } +}); + +// ??? Programming style question: should each of these classes be in separate files? +CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ + // collection is CourseUpdateCollection + events: { + "click .new-update-button" : "onNew", + "click .save-button" : "onSave", + "click .cancel-button" : "onCancel", + "click .edit-button" : "onEdit", + "click .delete-button" : "onDelete" + }, + + initialize: function() { + var self = this; + // instantiates an editor template for each update in the collection + window.templateLoader.loadRemoteTemplate("course_info_update", + // TODO Where should the template reside? how to use the static.url to create the path? + "/static/coffee/src/client_templates/course_info_update.html", + function (raw_template) { + self.template = _.template(raw_template); + self.render(); + } + ); + }, + + render: function () { + // iterate over updates and create views for each using the template + var updateEle = this.$el.find("#course-update-list"); + // remove and then add all children + $(updateEle).empty(); + var self = this; + this.collection.each(function (update) { + var newEle = self.template({ updateModel : update }); + $(updateEle).append(newEle); + }); + this.$el.find(".new-update-form").hide(); + return this; + }, + + onNew: function(event) { + // create new obj, insert into collection, and render this one ele overriding the hidden attr + var newModel = new CMS.Models.CourseUpdate(); + this.collection.add(newModel, {at : 0}); + + var newForm = this.template({ updateModel : newModel }); + var updateEle = this.$el.find("#course-update-list"); + $(updateEle).append(newForm); + $(newForm).find(".new-update-form").show(); + }, + + onSave: function(event) { + var targetModel = this.eventModel(event); + targetModel.set({ date : this.dateEntry(event).val(), content : this.contentEntry(event).val() }); + // push change to display, hide the editor, submit the change + $(this.dateDisplay(event)).val(targetModel.get('date')); + $(this.contentDisplay(event)).val(targetModel.get('content')); + $(this.editor(event)).hide(); + + targetModel.save(); + }, + + onCancel: function(event) { + // change editor contents back to model values and hide the editor + $(this.editor(event)).hide(); + var targetModel = this.eventModel(event); + $(this.dateEntry(event)).val(targetModel.get('date')); + $(this.contentEntry(event)).val(targetModel.get('content')); + }, + + onEdit: function(event) { + $(this.editor(event)).show(); + }, + + onDelete: function(event) { + // TODO ask for confirmation + // remove the dom element and delete the model + var targetModel = this.eventModel(event); + this.modelDom(event).remove(); + var cacheThis = this; + targetModel.destroy({success : function (model, response) { + cacheThis.collection.fetch({success : function() {cacheThis.render();}}); + } + }); + }, + + // Dereferencing from events to screen elements + eventModel: function(event) { + // not sure if it should be currentTarget or delegateTarget + return this.collection.getByCid($(event.currentTarget).attr("name")); + }, + + modelDom: function(event) { + return $(event.currentTarget).closest("li"); + }, + + editor: function(event) { + var li = $(event.currentTarget).closest("li"); + if (li) return $(li).find("form").first(); + }, + + dateEntry: function(event) { + var li = $(event.currentTarget).closest("li"); + if (li) return $(li).find("#date-entry").first(); + }, + + contentEntry: function(event) { + return $(event.currentTarget).closest("li").find(".new-update-content").first(); + }, + + dateDisplay: function(event) { + return $(event.currentTarget).closest("li").find("#date-display").first(); + }, + + contentDisplay: function(event) { + return $(event.currentTarget).closest("li").find(".update-contents").first(); + } + +}); + \ No newline at end of file diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html index 72facf7c2e..f68aff008b 100644 --- a/cms/templates/course_info.html +++ b/cms/templates/course_info.html @@ -1,16 +1,31 @@ <%inherit file="base.html" /> +<%namespace name='static' file='static_content.html'/> + <%block name="title">Course Info <%block name="jsextra"> + + + + @@ -19,10 +34,10 @@ $(document).ready(function(){

                                Course Info

                                -
                                +

                                Updates

                                New Update -
                                +
                                  diff --git a/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml b/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml index fa3ed606bd..c6958ed887 100644 --- a/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml +++ b/common/lib/xmodule/xmodule/templates/courseinfo/empty.yaml @@ -1,5 +1,5 @@ --- metadata: display_name: Empty -data: "

                                  This is where you can add additional information about your course.

                                  " +data: "
                                    " children: [] \ No newline at end of file From 8c8be3e82abfab8cb1343c33b11f198f1731c122 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Mon, 19 Nov 2012 09:48:41 -0500 Subject: [PATCH 035/243] Cleaned up imports --- common/lib/xmodule/xmodule/course_module.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index e4d2961723..5b19d57dce 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -1,18 +1,16 @@ -from fs.errors import ResourceNotFoundError -import logging -import json +from cStringIO import StringIO from lxml import etree from path import path # NOTE (THK): Only used for detecting presence of syllabus -import requests -import time -from cStringIO import StringIO - -from xmodule.util.decorators import lazyproperty +from xmodule.graders import grader_from_conf from xmodule.modulestore import Location from xmodule.seq_module import SequenceDescriptor, SequenceModule -from xmodule.xml_module import XmlDescriptor from xmodule.timeparse import parse_time, stringify_time -from xmodule.graders import grader_from_conf +from xmodule.util.decorators import lazyproperty +import json +import logging +import requests +import time + log = logging.getLogger(__name__) From ab87ff6b209973904bc4fa2d25af35a5a4e96c76 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 14:55:32 -0500 Subject: [PATCH 036/243] settings - field alignment, radio/checkbox formatting --- cms/static/sass/_settings.scss | 74 ++++++++-- cms/static/sass/_variables.scss | 3 +- cms/templates/settings.html | 240 +++++++++++++------------------- 3 files changed, 160 insertions(+), 157 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 6c3413472c..48b8b6b050 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -122,6 +122,7 @@ &.long { width: 100%; + min-width: 400px; } &.tall { @@ -144,8 +145,9 @@ } &:disabled { - color: $darkGrey; - background: $lightGrey; + border-color: $mediumGrey; + color: $mediumGrey; + background: #fff; } } @@ -170,7 +172,7 @@ margin-bottom: 15px; } - .ui-status-input-checkbox, .ui-status-input-radio { + .ui-status-input-checkbox input, .ui-status-input-radio input { position: absolute; top: -9999px; left: -9999px; @@ -180,7 +182,7 @@ cursor: pointer; } - .ui-status-input-checkbox ~ label, .ui-status-input-radio ~ label { + .ui-status-input-checkbox input ~ label, .ui-status-input-radio input ~ label { position: relative; left: -30px; display: inline-block; @@ -209,21 +211,25 @@ } .ui-status-indic { - position: relative; - top: 2px; z-index: 10; display: inline-block; height: 15px; width: 15px; border: 2px; - background: $offBlack; opacity: 0.50; - @include border-radius(50px); @include box-sizing(border-box); @include transition(opacity 0.25s ease-in-out); } - .ui-status-input-checkbox:checked ~ label, .ui-status-input-radio:checked ~ label { + .ss-check { + color: $green; + } + + .ss-delete { + color: $red; + } + + .ui-status-input-checkbox input:checked ~ label, .ui-status-input-radio input:checked ~ label { opacity: 0.99; &:after { @@ -237,9 +243,19 @@ } } - .ui-status-input-checkbox:checked ~ .ui-status-indic, .ui-status-input-radio:checked ~ .ui-status-indic { + .ui-status-input-checkbox input:checked ~ .ui-status-indic, .ui-status-input-radio input:checked ~ .ui-status-indic { + background: transparent url('/static/img/.png') 0 0 no-repeat; opacity: 0.99; } + + // disabled + .ui-status-input-checkbox input:disabled, .ui-status-input-radio input:disabled { + color: $mediumGrey; + } + + .ui-status-input-checkbox input:disabled ~ .ui-status-indic, .ui-status-input-radio input:disabled ~ .ui-status-indic { + color: $mediumGrey; + } } .tip { @@ -309,6 +325,7 @@ .group { margin-bottom: 10px; + max-width: 175px; &:last-child { margin-bottom: 0; @@ -317,6 +334,10 @@ input, .input, textarea { } + + .tip-stacked { + margin-top: 0; + } } } @@ -325,7 +346,8 @@ .group { input, .input, textarea { - width: 100%; + min-width: 370px; + width: 370px; } } } @@ -408,6 +430,7 @@ .remove-item { clear: both; display: block; + margin-top: 10px; opacity: 0.75; font-size: 13px; text-align: right; @@ -430,7 +453,21 @@ padding: 10px; @include box-sizing(border-box); @include border-radius(5px); + font-size: 14px; background: tint($lightGrey, 50%); + @include clearfix(); + + .doc-filename { + display: inline-block; + width: 220px; + overflow: hidden; + text-overflow: ellipsis; + } + + .remove-doc-data { + display: inline-block; + width: 150px; + } } } @@ -479,14 +516,15 @@ } .current-faculty-photo { - height: 115px; - width: 115px; overflow: hidden; + padding: 0; img { display: block; - min-height: 100%; - max-width: 100%; + @include box-shadow(0 1px 3px rgba(0,0,0,0.1)); + padding: 10px; + border: 2px solid $mediumGrey; + background: #fff; } } } @@ -579,6 +617,12 @@ .divide { display: none; } + + i.ss-icon { + position: relative; + top: 1px; + margin-right: 5px; + } } diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index 8ab5e0d703..8666dc192c 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -16,7 +16,8 @@ $error-red: rgb(253, 87, 87); $offBlack: #3c3c3c; $blue: #5597dd; $orange: #edbd3c; -$red: #e23c3e; +$red: #b20610; +$green: #108614; $lightGrey: #edf1f5; $mediumGrey: #ced2db; $darkGrey: #8891a1; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 1cc0ae8ea0..8d618fcfc1 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -98,6 +98,7 @@ $(this).text(min + '-' + max); }); } + @@ -133,7 +134,8 @@
                                    - + + This is used in your course URL, and cannot be changed
                                    @@ -142,7 +144,8 @@
                                    - + + This is used in your course URL, and cannot be changed
                                    @@ -151,8 +154,9 @@
                                    - + e.g. 101x + This is used in your course URL, and cannot be changed

                              @@ -167,43 +171,41 @@
                              - +

                              Course Dates:

                              +
                              -
                              - - First day the course begins +
                              +
                              + + + First day the course begins +
                              + +
                              + + + Last day the course is active +
                              - +

                              Enrollment Dates:

                              +
                              -
                              - - First day the course begins - The start date of the course cannot be after the end date of the course -
                              -
                              -
                              +
                              +
                              + + + First day students can enroll +
                              -
                              - -
                              -
                              - - Last day the course begins -
                              -
                              -
                              - -
                              - -
                              -
                              - - Last day the course begins - The end date of the course cannot be before the start date of the course +
                              + + + Last day students can enroll +
                              @@ -227,36 +229,6 @@ Delete Milestone -
                            3. -
                              - - - A milestone date cannot be after the end of a course -
                              - -
                              - - -
                              - - Delete Milestone -
                            4. - -
                            5. -
                              - - - A milestone date cannot be before the start of a course -
                              - -
                              - - -
                              - - Delete Milestone -
                            6. -
                            7. @@ -281,10 +253,10 @@
                              @@ -330,10 +302,10 @@
                              @@ -515,37 +487,36 @@
                              • -
                                +
                                -
                                +
                                -
                                +
                                -
                                +
                                - + A brief description of your education, experience, and expertise
                                @@ -554,21 +525,21 @@
                              • -
                                +
                                -
                                +
                                -
                                +
                                @@ -580,11 +551,11 @@
                                -
                                +
                                - + A brief description of your education, experience, and expertise
                                @@ -825,22 +796,22 @@

                                Problem Randomization:

                                -
                                - +
                                +
                                randomize all problems
                                -
                                - +
                                +
                                do not randomize problems
                                -
                                - +
                                +
                                randomize problems per student @@ -852,15 +823,15 @@

                                Show Answers:

                                -
                                - +
                                +
                                Answers will be shown after the number of attempts has been met
                                -
                                - +
                                +
                                Answers will never be shown, regardless of attempts @@ -890,22 +861,22 @@

                                Problem Randomization:

                                -
                                - +
                                +
                                randomize all problems
                                -
                                - +
                                +
                                do not randomize problems
                                -
                                - +
                                +
                                randomize problems per student @@ -917,15 +888,15 @@

                                Show Answers:

                                -
                                - +
                                +
                                Answers will be shown after the number of attempts has been met
                                -
                                - +
                                +
                                Answers will never be shown, regardless of attempts @@ -955,22 +926,22 @@

                                Problem Randomization:

                                -
                                - +
                                +
                                randomize all problems
                                -
                                - +
                                +
                                do not randomize problems
                                -
                                - +
                                +
                                randomize problems per student @@ -982,15 +953,15 @@

                                Show Answers:

                                -
                                - +
                                +
                                Answers will be shown after the number of attempts has been met
                                -
                                - +
                                +
                                Answers will never be shown, regardless of attempts @@ -1020,22 +991,22 @@

                                Problem Randomization:

                                -
                                - +
                                +
                                randomize all problems
                                -
                                - +
                                +
                                do not randomize problems
                                -
                                - +
                                +
                                randomize problems per student @@ -1047,15 +1018,15 @@

                                Show Answers:

                                -
                                - +
                                +
                                Answers will be shown after the number of attempts has been met
                                -
                                - +
                                +
                                Answers will never be shown, regardless of attempts @@ -1089,21 +1060,20 @@

                                Anonymous Discussions:

                                -
                                - -
                                +
                                + +
                                Students and faculty will be able to post anonymously
                                -
                                - -
                                +
                                + +
                                Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous
                                -
                                @@ -1166,18 +1136,6 @@
                                - -
                                -

                                Create Discussion
                                Categories for Each
                                Course Unit

                                - -
                                -
                                - -
                                - - This option is automatically set currently -
                                -
                                From c7784e30fd7977820a95a4fb1776b60ea518b422 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 14:59:25 -0500 Subject: [PATCH 037/243] settings - radio/checkbox UI - wip --- cms/static/sass/_settings.scss | 8 +++----- cms/templates/settings.html | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 48b8b6b050..2d381b5454 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -213,11 +213,9 @@ .ui-status-indic { z-index: 10; display: inline-block; - height: 15px; - width: 15px; - border: 2px; + height: 30px; + width: 20px; opacity: 0.50; - @include box-sizing(border-box); @include transition(opacity 0.25s ease-in-out); } @@ -244,7 +242,7 @@ } .ui-status-input-checkbox input:checked ~ .ui-status-indic, .ui-status-input-radio input:checked ~ .ui-status-indic { - background: transparent url('/static/img/.png') 0 0 no-repeat; + background: transparent url('../images/correct-icon.png') 0 0 no-repeat; opacity: 0.99; } diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 8d618fcfc1..ab603923ee 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -1062,14 +1062,14 @@
                                -
                                +
                                Students and faculty will be able to post anonymously
                                -
                                +
                                Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous
                                From d37030791785594c353eac7c50fbd98d9346d582 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 19 Nov 2012 15:48:57 -0500 Subject: [PATCH 038/243] settings - simplified course details fields per Mark's feedback and radio/checkbox UI --- cms/static/sass/_settings.scss | 6 +- cms/templates/settings.html | 306 ++++++--------------------------- 2 files changed, 54 insertions(+), 258 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 2d381b5454..d2c8ac514b 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -211,10 +211,11 @@ } .ui-status-indic { + background: transparent url('../images/correct-icon.png') 0 0 no-repeat; z-index: 10; display: inline-block; - height: 30px; - width: 20px; + height: 20px; + width: 30px; opacity: 0.50; @include transition(opacity 0.25s ease-in-out); } @@ -464,6 +465,7 @@ .remove-doc-data { display: inline-block; + margin-top: 0; width: 150px; } } diff --git a/cms/templates/settings.html b/cms/templates/settings.html index ab603923ee..5dae8a5d09 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -135,7 +135,7 @@
                                - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                                @@ -145,7 +145,7 @@
                                - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                                @@ -156,7 +156,7 @@
                                e.g. 101x - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                                @@ -210,44 +210,6 @@
                                -
                                -

                                Milestones:

                                - -
                                -
                                  -
                                • -
                                  - - -
                                  - -
                                  - - -
                                  - - Delete Milestone -
                                • - -
                                • -
                                  - - -
                                  - -
                                  - - -
                                  -
                                • -
                                - - - New Course Milestone - -
                                -
                                -
                                @@ -281,22 +243,12 @@
                                - - Detailed summary of concepts and lessons covered + + Introductions, prerequisites, FAQs that are used on your course summary page
                                -
                                - -
                                -
                                - - 1-2 sentences used to introduce your class to perspective students -
                                -
                                -
                                -
                                @@ -326,16 +278,6 @@ Expectations of the students taking this course -
                                - -
                                -
                                - - Supplies, software, and set-up that students will need -
                                -
                                -
                                -
                                @@ -345,132 +287,6 @@
                                - -
                                -

                                Textbooks:

                                - -
                                -
                                  -
                                • -
                                  - - -
                                  - -
                                  - - -
                                  - - Delete Textbook -
                                • - -
                                • -
                                  - - -
                                  - -
                                  - - -
                                  - -
                                • -
                                - - - New Textbook - -
                                -
                                - -
                                -

                                Prerequisites:

                                - -
                                -
                                  -
                                • -
                                  - - -
                                  - -
                                  - - -
                                  - - Delete Prerequisite -
                                • - -
                                • -
                                  - - -
                                  - -
                                  - - -
                                  -
                                • -
                                - - - New Prerequisite - -
                                -
                                - - -
                                - -
                                -
                                -

                                More Information

                                - Other helpful information about the course -
                                - -
                                -

                                FAQs:

                                - -
                                - - - - New Question & Answer - -
                                -
                                @@ -795,24 +611,21 @@

                                Problem Randomization:

                                -
                                -
                                - -
                                +
                                +
                                + randomize all problems
                                -
                                - -
                                +
                                + do not randomize problems
                                -
                                - -
                                +
                                + randomize problems per student
                                @@ -823,16 +636,14 @@

                                Show Answers:

                                -
                                - -
                                +
                                + Answers will be shown after the number of attempts has been met
                                -
                                - -
                                +
                                + Answers will never be shown, regardless of attempts
                                @@ -861,23 +672,20 @@

                                Problem Randomization:

                                -
                                - -
                                +
                                + randomize all problems
                                -
                                - -
                                +
                                + do not randomize problems
                                -
                                - -
                                +
                                + randomize problems per student
                                @@ -888,16 +696,14 @@

                                Show Answers:

                                -
                                - -
                                +
                                + Answers will be shown after the number of attempts has been met
                                -
                                - -
                                +
                                + Answers will never be shown, regardless of attempts
                                @@ -926,23 +732,20 @@

                                Problem Randomization:

                                -
                                - -
                                +
                                + randomize all problems
                                -
                                - -
                                +
                                + do not randomize problems
                                -
                                - -
                                +
                                + randomize problems per student
                                @@ -953,16 +756,14 @@

                                Show Answers:

                                -
                                - -
                                +
                                + Answers will be shown after the number of attempts has been met
                                -
                                - -
                                +
                                + Answers will never be shown, regardless of attempts
                                @@ -991,23 +792,20 @@

                                Problem Randomization:

                                -
                                - -
                                +
                                + randomize all problems
                                -
                                - -
                                +
                                + do not randomize problems
                                -
                                - -
                                +
                                + randomize problems per student
                                @@ -1018,16 +816,14 @@

                                Show Answers:

                                -
                                - -
                                +
                                + Answers will be shown after the number of attempts has been met
                                -
                                - -
                                +
                                + Answers will never be shown, regardless of attempts
                                @@ -1060,16 +856,14 @@

                                Anonymous Discussions:

                                -
                                - -
                                +
                                + Students and faculty will be able to post anonymously
                                -
                                - -
                                +
                                + Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous
                                From 241c9bb1acbbcdbb1dc18a1d5e03f81aaff692d1 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Mon, 19 Nov 2012 16:02:30 -0500 Subject: [PATCH 039/243] Checkpoint to rebase --- cms/static/js/models/course_relative.js | 59 + .../js/models/settings/course_detais.js | 40 + .../js/models/settings/course_settings.js | 13 + .../js/views/settings/main_settings_view.js | 29 + cms/static/sass/_settings.scss | 742 +++++++++++ cms/templates/settings.html | 1167 +++++++++++++++++ common/djangoapps/models/__init__.py | 0 common/djangoapps/models/course_relative.py | 25 + common/djangoapps/models/settings/__init__.py | 0 .../models/settings/course_details.py | 36 + 10 files changed, 2111 insertions(+) create mode 100644 cms/static/js/models/course_relative.js create mode 100644 cms/static/js/models/settings/course_detais.js create mode 100644 cms/static/js/models/settings/course_settings.js create mode 100644 cms/static/js/views/settings/main_settings_view.js create mode 100644 cms/static/sass/_settings.scss create mode 100644 cms/templates/settings.html create mode 100644 common/djangoapps/models/__init__.py create mode 100644 common/djangoapps/models/course_relative.py create mode 100644 common/djangoapps/models/settings/__init__.py create mode 100644 common/djangoapps/models/settings/course_details.py diff --git a/cms/static/js/models/course_relative.js b/cms/static/js/models/course_relative.js new file mode 100644 index 0000000000..c9f20d6789 --- /dev/null +++ b/cms/static/js/models/course_relative.js @@ -0,0 +1,59 @@ +CMS.Models.Location = Backbone.Models.extend({ + defaults: { + tag: "", + name: "", + course: "", + category: "", + name: "" + }, + toUrl: function(overrides) { + return + (overrides['tag'] ? overrides['tag'] : this.get('tag')) + "://" + + (overrides['name'] ? overrides['name'] : this.get('name')) + "/" + + (overrides['course'] ? overrides['course'] : this.get('course')) + "/" + + (overrides['category'] ? overrides['category'] : this.get('category')) + "/" + + (overrides['name'] ? overrides['name'] : this.get('name')) + "/"; + }, + _tagPattern = /[^:]+/g, + _fieldPattern = new RegExp('[^/]+','g'), + + parse: function(payload) { + if (payload instanceof Array) { + return { + tag: payload[0], + name: payload[1], + course: payload[2], + category: payload[3], + name: payload[4] + } + } + else if (payload instanceof String) { + var foundTag = this._tagPattern.exec(payload); + if (foundTag) { + this._fieldPattern.lastIndex = this._tagPattern.lastIndex; + return { + tag: foundTag, + name: this._fieldPattern.exec(payload), + course: this._fieldPattern.exec(payload), + category: this._fieldPattern.exec(payload), + name: this._fieldPattern.exec(payload) + } + } + else return null; + } + else { + return payload; + } + } +}); + +CMS.Models.CourseRelative = Backbone.Models.extend({ + defaults: { + course_location : null, // must never be null, but here to doc the field + idx : null // the index making it unique in the containing collection (no implied sort) + } +}); + +CMS.Models.CourseRelativeCollection = Backbone.Collections.extend({ + model : CourseRelative +}); \ No newline at end of file diff --git a/cms/static/js/models/settings/course_detais.js b/cms/static/js/models/settings/course_detais.js new file mode 100644 index 0000000000..aa1fd1463d --- /dev/null +++ b/cms/static/js/models/settings/course_detais.js @@ -0,0 +1,40 @@ +CMS.Models.Settings.CourseDetails = Backbone.Models.extend({ + defaults: { + location : null, # a Location model, required + start_date: null, + end_date: null, + milestones: null, # a CourseRelativeCollection + syllabus: null, + overview: "", + statement: "", + intro_video: null, + requirements: "", + effort: null, # an int or null + textbooks: null, # a CourseRelativeCollection + prereqs: null, # a CourseRelativeCollection + faqs: null # a CourseRelativeCollection + }, + + // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) + parse: function(attributes) { + if (attributes['location']) { + attributes.location = new CMS.Models.Location(attributes.location); + }; + if (attributes['milestones']) { + attributes.milestones = new CMS.Models.CourseRelativeCollection(attributes.milestones); + }; + if (attributes['textbooks']) { + attributes.textbooks = new CMS.Models.CourseRelativeCollection(attributes.textbooks); + }; + if (attributes['prereqs']) { + attributes.prereqs = new CMS.Models.CourseRelativeCollection(attributes.prereqs); + }; + if (attributes['faqs']) { + attributes.faqs = new CMS.Models.CourseRelativeCollection(attributes.faqs); + }; + }, + + urlRoot: function() { + // TODO impl + } +}); diff --git a/cms/static/js/models/settings/course_settings.js b/cms/static/js/models/settings/course_settings.js new file mode 100644 index 0000000000..10a8f4df69 --- /dev/null +++ b/cms/static/js/models/settings/course_settings.js @@ -0,0 +1,13 @@ +CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ + // a container for the models representing the n possible tabbed states + defaults: { + courseLocation: null, + // NOTE: keep these sync'd w/ the data-section names in settings-page-menu + details: null, + faculty: null, + grading: null, + problems: null, + discussions: null + } + // write getters which get the relevant sub model from the server if not already loaded +}) \ No newline at end of file diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js new file mode 100644 index 0000000000..d910f4dbc0 --- /dev/null +++ b/cms/static/js/views/settings/main_settings_view.js @@ -0,0 +1,29 @@ +CMS.Views.Settings.Main = Backbone.View.extend({ + // Model class is CMS.Models.Settings.CourseSettings + // allow navigation between the tabs + events: { + 'click .settings-page-menu a': "showSettingsTab" + }, + initialize: function() { + // load templates + }, + render: function() { + // create any necessary subviews and put them onto the page + }, + + currentTab: null, + + showSettingsTab: function(e) { + this.currentTab = $(e.target).attr('data-section'); + $('.settings-page-section > section').hide(); + $('.settings-' + this.currentTab).show(); + $('.settings-page-menu .is-shown').removeClass('is-shown'); + $(e.target).addClass('is-shown'); + // fetch model for the tab if not loaded already + if (!this.model.has(this.currentTab)) { + // TODO disable screen until fetch completes? + this.model.retrieve(this.currentTab, function() { this.render(); }); + } + } + +}) \ No newline at end of file diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss new file mode 100644 index 0000000000..89f7841df0 --- /dev/null +++ b/cms/static/sass/_settings.scss @@ -0,0 +1,742 @@ +.settings { + .settings-overview { + @extend .window; + @include clearfix; + display: table; + width: 100%; + + // layout + .sidebar { + display: table-cell; + float: none; + width: 20%; + padding: 30px 0 30px 20px; + @include border-radius(3px 0 0 3px); + background: $lightGrey; + } + + .main-column { + display: table-cell; + float: none; + width: 80%; + padding: 30px 40px 30px 60px; + } + + .settings-page-menu { + a { + display: block; + padding-left: 20px; + line-height: 52px; + + &.is-shown { + background: #fff; + @include border-radius(5px 0 0 5px); + } + } + } + + .settings-page-section { + > .alert { + display: none; + + &.is-shown { + display: block; + } + } + + > section { + display: none; + margin-bottom: 40px; + + &.is-shown { + display: block; + } + + &:last-child { + border-bottom: none; + } + + > .title { + margin-bottom: 30px; + font-size: 28px; + font-weight: 300; + color: $blue; + } + + > section { + margin-bottom: 100px; + @include clearfix; + + header { + @include clearfix; + border-bottom: 1px solid $mediumGrey; + margin-bottom: 20px; + padding-bottom: 10px; + + h3 { + color: $darkGrey; + float: left; + + margin: 0 40px 0 0; + text-transform: uppercase; + } + + .detail { + float: right; + marign-top: 3px; + color: $mediumGrey; + font-size: 13px; + } + } + + &:last-child { + padding-bottom: 0; + border-bottom: none; + } + } + } + } + + // form basics + label, .label { + padding: 0; + border: none; + background: none; + font-size: 15px; + font-weight: 400; + + &.check-label { + display: inline; + margin-left: 10px; + } + + &.ranges { + margin-bottom: 20px; + } + } + + input, textarea { + @include transition(all 1s ease-in-out); + @include box-sizing(border-box); + font-size: 15px; + + &.long { + width: 100%; + } + + &.tall { + height: 200px; + } + + &.short { + width: 25%; + } + + &.date { + + } + + &:focus { + @include linear-gradient(tint($blue, 80%), tint($blue, 90%)); + border-color: $blue; + outline: 0; + } + + &:disabled { + color: $darkGrey; + background: $lightGrey; + } + } + + .input-default { + color: $darkGrey; + background: $lightGrey; + } + + ::-webkit-input-placeholder { + color: $mediumGrey; + font-size: 13px; + } + :-moz-placeholder { + color: $mediumGrey; + font-size: 13px; + } + + .field.ui-status { + + > .input { + display: block !important; + margin-bottom: 15px; + } + + .ui-status-input-checkbox, .ui-status-input-radio { + position: absolute; + top: -9999px; + left: -9999px; + } + + label { + cursor: pointer; + } + + .ui-status-input-checkbox ~ label, .ui-status-input-radio ~ label { + position: relative; + left: -30px; + display: inline-block; + z-index: 100; + margin: 0 0 0 5px; + padding-left: 30px; + color: $offBlack; + opacity: 0.50; + cursor: pointer; + @include transition(opacity 0.25s ease-in-out); + + &:before { + display: inline-block; + margin-right: 10px; + } + + &:after { + display: inline-block; + margin-left: 10px; + } + + ~ .tip { + margin-top: 0; + @include transition(color 0.25s ease-in-out); + } + } + + .ui-status-indic { + position: relative; + top: 2px; + z-index: 10; + display: inline-block; + height: 15px; + width: 15px; + border: 2px; + background: $offBlack; + opacity: 0.50; + @include border-radius(50px); + @include box-sizing(border-box); + @include transition(opacity 0.25s ease-in-out); + } + + .ui-status-input-checkbox:checked ~ label, .ui-status-input-radio:checked ~ label { + opacity: 0.99; + + &:after { + } + + &:before { + } + + ~ .tip { + color: $darkGrey; + } + } + + .ui-status-input-checkbox:checked ~ .ui-status-indic, .ui-status-input-radio:checked ~ .ui-status-indic { + opacity: 0.99; + } + } + + .tip { + color: $mediumGrey; + font-size: 13px; + } + + + // form layouts + .row { + margin-bottom: 30px; + padding-bottom: 30px; + border-bottom: 1px solid $lightGrey; + + &:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; + } + + // structural labels, not semantic labels per se + > label, .label { + display: inline-block; + vertical-align: top; + } + + // tips + .tip-inline { + display: inline-block; + margin-left: 10px; + } + + .tip-stacked { + display: block; + margin-top: 10px; + } + + // structural field, not semantic fields per se + .field { + display: inline-block; + width: 100%; + + > input, > textarea, .input { + display: inline-block; + + &:last-child { + margin-bottom: 0; + } + + .group { + input, textarea { + margin-bottom: 5px; + } + + .label, label { + font-size: 13px; + } + } + + // multi-field + &.multi { + display: block; + background: tint($lightGrey, 50%); + padding: 15px; + @include border-radius(4px); + @include box-sizing(border-box); + + .group { + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + + input, .input, textarea { + + } + } + } + + // multi stacked + &.multi-stacked { + + .group { + input, .input, textarea { + width: 100%; + } + } + } + + // multi-field inline + &.multi-inline { + @include clearfix; + + .group { + float: left; + margin-right: 20px; + + &:nth-child(2) { + margin-right: 0; + } + + .input, input, textarea { + width: 100%; + } + } + + .remove-item { + float: right; + } + } + } + + // input-list + .input-list { + + .input { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px dotted $lightGrey; + + &:last-child { + border: 0; + } + } + } + + // enumerated inputs + &.enum { + } + } + + // layout - aligned label/field pairs + &.row-col2 { + + > label, .label { + width: 200px; + } + + .field { + width: 400px; + } + + &.multi-inline { + @include clearfix; + + .group { + width: 170px; + } + } + } + } + + // editing controls - adding + .new-item, .replace-item { + clear: both; + display: block; + margin-top: 10px; + padding-bottom: 10px; + @include grey-button; + @include box-sizing(border-box); + } + + + // editing controls - removing + .remove-item { + clear: both; + display: block; + opacity: 0.75; + font-size: 13px; + text-align: right; + @include transition(opacity 0.25s ease-in-out); + + + &:hover { + color: $blue; + opacity: 0.99; + } + } + + // editing controls - preview + .input-existing { + display: block !important; + + .current { + width: 100%; + margin: 10px 0; + padding: 15px; + @include box-sizing(border-box); + @include border-radius(5px); + background: tint($blue, 80%); + } + } + + // specific sections + .settings-details { + + } + + .settings-faculty { + + .settings-faculty-members { + + > header { + display: none; + } + + .field .multi { + display: block; + margin-bottom: 40px; + padding: 20px; + background: tint($lightGrey, 50%); + @include border-radius(4px); + @include box-sizing(border-box); + } + + .course-faculty-list-item { + + .row { + + &:nth-child(4) { + padding-bottom: 0; + border-bottom: none; + } + } + } + + #course-faculty-bio-input { + margin-bottom: 0; + } + + .new-course-faculty-item { + } + + .current-faculty-photo { + height: 115px; + width: 115px; + overflow: hidden; + + img { + display: block; + min-height: 100%; + max-width: 100%; + } + } + } + } + + .settings-grading { + + + .course-grading-gradeweight, .course-grading-totalassignments, .course-grading-totalassignmentsdroppable { + + input { + width: 73px; + } + } + } + + .settings-handouts { + + } + + .settings-problems { + + > section { + + &.is-shown { + display: block; + } + } + } + + .settings-discussions { + + } + + // states + label.is-focused { + color: $blue; + @include transition(color 1s ease-in-out); + } + + // extras/abbreviations + // .settings-extras { + + // > header { + // cursor: pointer; + + // &.active { + + // } + // } + + // > div { + // display: none; + // @include transition(display 0.25s ease-in-out); + + // &.is-shown { + // display: block; + // } + // } + // } + + // misc + .divide { + display: none; + } + } + + + + h3 { + margin-bottom: 30px; + font-size: 15px; + font-weight: 700; + color: $blue; + } + + .grade-controls { + @include clearfix; + } + + .new-grade-button { + position: relative; + float: left; + display: block; + width: 29px; + height: 29px; + margin: 4px 10px 0 0; + border-radius: 20px; + border: 1px solid $darkGrey; + @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); + background-color: #d1dae3; + @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); + color: #6d788b; + + .plus-icon { + position: absolute; + top: 50%; + left: 50%; + margin-left: -6px; + margin-top: -6px; + } + } + + .grade-slider { + float: right; + width: 95%; + height: 80px; + + .grade-bar { + position: relative; + width: 100%; + height: 50px; + background: $lightGrey; + + .increments { + position: relative; + + li { + position: absolute; + top: 52px; + width: 30px; + margin-left: -15px; + font-size: 9px; + text-align: center; + + &.increment-0 { + left: 0; + } + + &.increment-10 { + left: 10%; + } + + &.increment-20 { + left: 20%; + } + + &.increment-30 { + left: 30%; + } + + &.increment-40 { + left: 40%; + } + + &.increment-50 { + left: 50%; + } + + &.increment-60 { + left: 60%; + } + + &.increment-70 { + left: 70%; + } + + &.increment-80 { + left: 80%; + } + + &.increment-90 { + left: 90%; + } + + &.increment-100 { + left: 100%; + } + } + } + + .grades { + position: relative; + + li { + position: absolute; + top: 0; + height: 50px; + text-align: right; + + &:hover, + &.is-dragging { + .remove-button { + display: block; + } + } + + &.is-dragging { + + + } + + .remove-button { + display: none; + position: absolute; + top: -17px; + right: 1px; + height: 17px; + font-size: 10px; + } + + &:nth-child(1) { + background: #4fe696; + } + + &:nth-child(2) { + background: #ffdf7e; + } + + &:nth-child(3) { + background: #ffb657; + } + + &:nth-child(4) { + background: #fb336c; + } + + &:nth-child(5) { + background: #ef54a1; + } + + .letter-grade { + display: block; + margin: 10px 15px 0 0; + font-size: 16px; + font-weight: 700; + line-height: 14px; + } + + .range { + display: block; + margin-right: 15px; + font-size: 10px; + line-height: 12px; + } + + .drag-bar { + position: absolute; + top: 0; + right: -1px; + height: 50px; + width: 2px; + background-color: #fff; + @include box-shadow(-1px 0 3px rgba(0,0,0,0.1)); + + cursor: ew-resize; + @include transition(none); + + &:hover { + width: 6px; + right: -2px; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/cms/templates/settings.html b/cms/templates/settings.html new file mode 100644 index 0000000000..3f7094fad8 --- /dev/null +++ b/cms/templates/settings.html @@ -0,0 +1,1167 @@ +<%inherit file="base.html" /> +<%block name="bodyclass">settings +<%block name="title">Settings + +<%namespace name='static' file='static_content.html'/> + +<%block name="jsextra"> + + + + + + + + + + + + + + +<%block name="content"> + +
                                +
                                +

                                Settings

                                +
                                + +
                                + +
                                +

                                Course Details

                                + +
                                +
                                +

                                Basic Information

                                + The nuts and bolts of your course +
                                + +
                                + +
                                +
                                + +
                                +
                                +
                                + +
                                + +
                                +
                                + +
                                +
                                +
                                + +
                                + +
                                +
                                + + e.g. 101x +
                                +
                                +
                                +
                                + +
                                + +
                                +
                                +

                                Course Schedule

                                + Important steps and segments of your your course +
                                + +
                                + +
                                +
                                + + First day the class begins +
                                +
                                +
                                + +
                                + +
                                +
                                + + Last day the class begins +
                                +
                                +
                                + +
                                +

                                Milestones:

                                + +
                                +
                                  +
                                • +
                                  + + +
                                  + +
                                  + + +
                                  + + Delete Milestone +
                                • + +
                                • +
                                  + + +
                                  + +
                                  + + +
                                  +
                                • +
                                + + + New Course Milestone + +
                                +
                                + +
                                + +
                                +
                                +
                                + CS184x_syllabus.pdf +
                                + + Delete Syllabus + + PDF formatting preferred +
                                + +
                                + + Upload Syllabus + + PDF formatting preferred +
                                +
                                +
                                +
                                + +
                                + +
                                +
                                +

                                Introducing Your Course

                                + Information for perspective students +
                                + +
                                + +
                                +
                                + + Detailed summary of concepts and lessons covered +
                                +
                                +
                                + +
                                + +
                                +
                                + + 1-2 sentences used to introduce your class to perspective students +
                                +
                                +
                                + +
                                + +
                                +
                                +
                                + +
                                + + Delete Video +
                                + +
                                + + Upload Video + + Video restrictions go here +
                                +
                                +
                                +
                                + +
                                + +
                                +
                                +

                                Requirements

                                + Expectations of the students taking this course +
                                + +
                                + +
                                +
                                + + Supplies, software, and set-up that students will need +
                                +
                                +
                                + +
                                + +
                                +
                                + + Time students should spend on all course work +
                                +
                                +
                                + +
                                +

                                Textbooks:

                                + +
                                +
                                  +
                                • +
                                  + + +
                                  + +
                                  + + +
                                  + + Delete Textbook +
                                • + +
                                • +
                                  + + +
                                  + +
                                  + + +
                                  + +
                                • +
                                + + + New Textbook + +
                                +
                                + +
                                +

                                Prerequisites:

                                + +
                                +
                                  +
                                • +
                                  + + +
                                  + +
                                  + + +
                                  + + Delete Prerequisite +
                                • + +
                                • +
                                  + + +
                                  + +
                                  + + +
                                  +
                                • +
                                + + + New Prerequisite + +
                                +
                                +
                                + +
                                + +
                                +
                                +

                                More Information

                                + Other helpful information about the course +
                                + +
                                +

                                FAQs:

                                + +
                                + + + + New Question & Answer + +
                                +
                                +
                                +
                                + +
                                +

                                Faculty

                                + +
                                +
                                +

                                Faculty Members

                                + Individuals instructing and help with this course +
                                + +
                                +
                                + + + + New Faculty Member + +
                                +
                                +
                                + +
                                + +
                                +

                                Grading

                                + +
                                +
                                +

                                Overall Grade Range

                                + Course grade ranges and their values +
                                + +
                                + +
                                + +
                                +
                                +
                                  +
                                1. 0
                                2. +
                                3. 10
                                4. +
                                5. 20
                                6. +
                                7. 30
                                8. +
                                9. 40
                                10. +
                                11. 50
                                12. +
                                13. 60
                                14. +
                                15. 70
                                16. +
                                17. 80
                                18. +
                                19. 90
                                20. +
                                21. 100
                                22. +
                                +
                                  +
                                1. + A + 81-100 + remove +
                                2. +
                                3. + B + 71-80 + + remove +
                                4. +
                                5. + C + 0-70 + + remove +
                                6. +
                                7. + F + 0-50 + + remove +
                                8. +
                                +
                                +
                                +
                                + +
                                +
                                + +
                                +
                                +

                                General Grading

                                + Deadlines and Requirements +
                                + +
                                + + +
                                +
                                + + Boston, MA Local Time (UTC/GMT -5 hours) + Convert to your time zone +
                                +
                                +
                                + +
                                + + +
                                +
                                + + e.g. +5 minutes +
                                +
                                +
                                +
                                + +
                                +
                                +

                                Lesson Exercises

                                + Grading in-lesson question & problems +
                                + +
                                + + +
                                +
                                + + e.g. 25% +
                                +
                                +
                                + +
                                + + +
                                +
                                + + total exercises assigned +
                                +
                                +
                                + +
                                + + +
                                +
                                + + total exercises that won't be graded +
                                +
                                +
                                +
                                + +
                                +
                                +

                                Labs

                                + Grading in-lesson question & problems +
                                + +
                                + + +
                                +
                                + + e.g. 25% +
                                +
                                +
                                + +
                                + + +
                                +
                                + + total labs assigned +
                                +
                                +
                                + +
                                + + +
                                +
                                + + total labs that won't be graded +
                                +
                                +
                                +
                                + +
                                +
                                +

                                Exams

                                + Grading in-lesson question & problems +
                                + +
                                + + +
                                +
                                + + e.g. 50% +
                                +
                                +
                                + +
                                + + +
                                +
                                + + total exams held +
                                +
                                +
                                + +
                                + + +
                                +
                                + + total exams that won't be graded +
                                +
                                +
                                +
                                +
                                + +
                                +

                                Problems

                                + +
                                +
                                +

                                General Settings

                                + Course-wide settings for all problems +
                                + +
                                +

                                Problem Randomization:

                                + +
                                +
                                + +
                                + + randomize all problems +
                                + +
                                + +
                                + + do not randomize problems +
                                + +
                                + +
                                + + randomize problems per student +
                                +
                                +
                                + +
                                +

                                Show Answers:

                                + +
                                +
                                + +
                                + + Answers will be shown after the number of attempts has been met +
                                + +
                                + +
                                + + Answers will never be shown, regardless of attempts +
                                +
                                +
                                + +
                                + + +
                                +
                                + + To set infinite atttempts, use "0" +
                                +
                                +
                                +
                                + +
                                +
                                +

                                Lesson Exercises

                                + In-lesson question & problem specific rules +
                                + +
                                +

                                Problem Randomization:

                                + +
                                +
                                + +
                                + + randomize all problems +
                                + +
                                + +
                                + + do not randomize problems +
                                + +
                                + +
                                + + randomize problems per student +
                                +
                                +
                                + +
                                +

                                Show Answers:

                                + +
                                +
                                + +
                                + + Answers will be shown after the number of attempts has been met +
                                + +
                                + +
                                + + Answers will never be shown, regardless of attempts +
                                +
                                +
                                + +
                                + + +
                                +
                                + + To set infinite atttempts, use "0" +
                                +
                                +
                                +
                                + +
                                +
                                +

                                Labs

                                + Exploratory assignment-specific rules +
                                + +
                                +

                                Problem Randomization:

                                + +
                                +
                                + +
                                + + randomize all problems +
                                + +
                                + +
                                + + do not randomize problems +
                                + +
                                + +
                                + + randomize problems per student +
                                +
                                +
                                + +
                                +

                                Show Answers:

                                + +
                                +
                                + +
                                + + Answers will be shown after the number of attempts has been met +
                                + +
                                + +
                                + + Answers will never be shown, regardless of attempts +
                                +
                                +
                                + +
                                + + +
                                +
                                + + To set infinite atttempts, use "0" +
                                +
                                +
                                +
                                + +
                                +
                                +

                                Exams

                                + Mid-term and final exam-specific rules +
                                + +
                                +

                                Problem Randomization:

                                + +
                                +
                                + +
                                + + randomize all problems +
                                + +
                                + +
                                + + do not randomize problems +
                                + +
                                + +
                                + + randomize problems per student +
                                +
                                +
                                + +
                                +

                                Show Answers:

                                + +
                                +
                                + +
                                + + Answers will be shown after the number of attempts has been met +
                                + +
                                + +
                                + + Answers will never be shown, regardless of attempts +
                                +
                                +
                                + +
                                + + +
                                +
                                + + To set infinite atttempts, use "0" +
                                +
                                +
                                +
                                +
                                + +
                                +

                                Discussions

                                + +
                                +
                                +

                                General Settings

                                + Course-wide settings for online discussion +
                                + +
                                +

                                Anonymous Discussions:

                                + +
                                +
                                + +
                                + + Students and faculty will be able to post anonymously +
                                + +
                                + +
                                + + Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous +
                                +
                                +
                                +
                                + +
                                +
                                +

                                Discussion Categories

                                + +
                                + + + + New Discussion Category + +
                                +
                                + +
                                +

                                Create Discussion Categories per Unit

                                + +
                                +
                                + +
                                + + This option is automatically set currently +
                                +
                                +
                                +
                                +
                                +
                                +
                                +
                                +
                                + diff --git a/common/djangoapps/models/__init__.py b/common/djangoapps/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/models/course_relative.py b/common/djangoapps/models/course_relative.py new file mode 100644 index 0000000000..4dfb83d183 --- /dev/null +++ b/common/djangoapps/models/course_relative.py @@ -0,0 +1,25 @@ +class CourseRelativeMember: + def __init__(self, location, idx): + self.course_location = location # a Location obj + self.idx = idx # which milestone this represents. Hopefully persisted # so we don't have race conditions + +### ??? If 2+ courses use the same textbook or other asset, should they point to the same db record? +class linked_asset(CourseRelativeMember): + """ + Something uploaded to our asset lib which has a name/label and location. Here it's tracked by course and index, but + we could replace the label/url w/ a pointer to a real asset and keep the join info here. + """ + def __init__(self, location, idx): + CourseRelativeMember.__init__(self, location, idx) + self.label = "" + self.url = None + +class summary_detail_pair(CourseRelativeMember): + """ + A short text with an arbitrary html descriptor used for paired label - details elements. + """ + def __init__(self, location, idx): + CourseRelativeMember.__init__(self, location, idx) + self.summary = "" + self.detail = "" + \ No newline at end of file diff --git a/common/djangoapps/models/settings/__init__.py b/common/djangoapps/models/settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/models/settings/course_details.py b/common/djangoapps/models/settings/course_details.py new file mode 100644 index 0000000000..95ad22efb9 --- /dev/null +++ b/common/djangoapps/models/settings/course_details.py @@ -0,0 +1,36 @@ +from common.djangoapps.models.course_relative import CourseRelativeMember + +### A basic question is whether to break the details into schedule, intro, requirements, and misc sub objects +class CourseDetails: + def __init__(self, location): + self.course_location = location # a Location obj + self.start_date = None + self.end_date = None + self.milestones = [] + self.syllabus = None # a pdf file asset + self.overview = "" # html to render as the overview + self.statement = "" + self.intro_video = None # a video pointer + self.requirements = "" # html + self.effort = None # int hours/week + self.textbooks = [] # linked_asset + self.prereqs = [] # linked_asset + self.faqs = [] # summary_detail_pair + + @classmethod + def fetch(cls, course_location): + """ + Fetch the course details for the given course from persistence and return a CourseDetails model. + """ + course = cls(course_location) + + # TODO implement + + return course + +class CourseMilestone(CourseRelativeMember): + def __init__(self, location, idx): + CourseRelativeMember.__init__(self, location, idx) + self.date = None + self.description = "" + From f29f605f62833a2c955d4a45a081df5104e36066 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 20 Nov 2012 08:40:45 -0500 Subject: [PATCH 040/243] settings - refactored radio/checkbox input formatting --- cms/static/sass/_settings.scss | 136 ++++++-------------- cms/templates/settings.html | 228 +++++++++++++++++++++------------ 2 files changed, 189 insertions(+), 175 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index d2c8ac514b..855f9f6009 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -151,6 +151,11 @@ } } + input[type="checkbox"], input[type="radio"] { + + } + + .input-default input, .input-default textarea { color: $mediumGrey; background: $lightGrey; @@ -165,98 +170,6 @@ font-size: 13px; } - .field.ui-status { - - > .input { - display: block !important; - margin-bottom: 15px; - } - - .ui-status-input-checkbox input, .ui-status-input-radio input { - position: absolute; - top: -9999px; - left: -9999px; - } - - label { - cursor: pointer; - } - - .ui-status-input-checkbox input ~ label, .ui-status-input-radio input ~ label { - position: relative; - left: -30px; - display: inline-block; - z-index: 100; - margin: 0 0 0 5px; - padding-left: 30px; - color: $offBlack; - opacity: 0.50; - cursor: pointer; - @include transition(opacity 0.25s ease-in-out); - - &:before { - display: inline-block; - margin-right: 10px; - } - - &:after { - display: inline-block; - margin-left: 10px; - } - - ~ .tip { - margin-top: 0; - @include transition(color 0.25s ease-in-out); - } - } - - .ui-status-indic { - background: transparent url('../images/correct-icon.png') 0 0 no-repeat; - z-index: 10; - display: inline-block; - height: 20px; - width: 30px; - opacity: 0.50; - @include transition(opacity 0.25s ease-in-out); - } - - .ss-check { - color: $green; - } - - .ss-delete { - color: $red; - } - - .ui-status-input-checkbox input:checked ~ label, .ui-status-input-radio input:checked ~ label { - opacity: 0.99; - - &:after { - } - - &:before { - } - - ~ .tip { - color: $darkGrey; - } - } - - .ui-status-input-checkbox input:checked ~ .ui-status-indic, .ui-status-input-radio input:checked ~ .ui-status-indic { - background: transparent url('../images/correct-icon.png') 0 0 no-repeat; - opacity: 0.99; - } - - // disabled - .ui-status-input-checkbox input:disabled, .ui-status-input-radio input:disabled { - color: $mediumGrey; - } - - .ui-status-input-checkbox input:disabled ~ .ui-status-indic, .ui-status-input-radio input:disabled ~ .ui-status-indic { - color: $mediumGrey; - } - } - .tip { color: $mediumGrey; font-size: 13px; @@ -388,6 +301,42 @@ } } + //radio buttons and checkboxes + .input-radio { + @include clearfix(); + + input { + display: block; + float: left; + margin-right: 10px; + } + + .copy { + position: relative; + top: -5px; + float: left; + width: 350px; + } + + label { + display: block; + margin-bottom: 0; + } + + .tip { + display: block; + margin-top: 0; + } + + .message-error { + + } + } + + .input-checkbox { + + } + // enumerated inputs &.enum { } @@ -405,7 +354,7 @@ } &.multi-inline { - @include clearfix; + @include clearfix(); .group { width: 170px; @@ -516,7 +465,6 @@ } .current-faculty-photo { - overflow: hidden; padding: 0; img { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 5dae8a5d09..8ac9460f62 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -303,21 +303,21 @@
                                • -
                                  +
                                  -
                                  +
                                  -
                                  +
                                  @@ -329,7 +329,7 @@
                                  -
                                  +
                                  @@ -341,21 +341,21 @@
                                • -
                                  +
                                  -
                                  +
                                  -
                                  +
                                  @@ -367,7 +367,7 @@
                                  -
                                  +
                                  @@ -614,20 +614,29 @@
                                  - - randomize all problems + +
                                  + + randomize all problems +
                                  -
                                  +
                                  - - do not randomize problems + +
                                  + + do not randomize problems +
                                  -
                                  +
                                  - - randomize problems per student + +
                                  + + randomize problems per student +
                                  @@ -635,17 +644,23 @@

                                  Show Answers:

                                  -
                                  -
                                  +
                                  +
                                  - - Answers will be shown after the number of attempts has been met + +
                                  + + Answers will be shown after the number of attempts has been met +
                                  -
                                  +
                                  - - Answers will never be shown, regardless of attempts + +
                                  + + Answers will never be shown, regardless of attempts +
                                  @@ -671,23 +686,32 @@

                                  Problem Randomization:

                                  -
                                  -
                                  +
                                  +
                                  - - randomize all problems + +
                                  + + randomize all problems +
                                  -
                                  +
                                  - - do not randomize problems + +
                                  + + do not randomize problems +
                                  -
                                  +
                                  - - randomize problems per student + +
                                  + + randomize problems per student +
                                  @@ -695,17 +719,23 @@

                                  Show Answers:

                                  -
                                  -
                                  +
                                  +
                                  - - Answers will be shown after the number of attempts has been met + +
                                  + + Answers will be shown after the number of attempts has been met +
                                  -
                                  +
                                  - - Answers will never be shown, regardless of attempts + +
                                  + + Answers will never be shown, regardless of attempts +
                                  @@ -731,23 +761,32 @@

                                  Problem Randomization:

                                  -
                                  -
                                  +
                                  +
                                  - - randomize all problems + +
                                  + + randomize all problems +
                                  -
                                  +
                                  - - do not randomize problems + +
                                  + + do not randomize problems +
                                  -
                                  +
                                  - - randomize problems per student + +
                                  + + randomize problems per student +
                                  @@ -755,17 +794,23 @@

                                  Show Answers:

                                  -
                                  -
                                  +
                                  +
                                  - - Answers will be shown after the number of attempts has been met + +
                                  + + Answers will be shown after the number of attempts has been met +
                                  -
                                  +
                                  - - Answers will never be shown, regardless of attempts + +
                                  + + Answers will never be shown, regardless of attempts +
                                  @@ -791,23 +836,32 @@

                                  Problem Randomization:

                                  -
                                  -
                                  +
                                  +
                                  - - randomize all problems + +
                                  + + randomize all problems +
                                  -
                                  +
                                  - - do not randomize problems + +
                                  + + do not randomize problems +
                                  -
                                  +
                                  - - randomize problems per student + +
                                  + + randomize problems per student +
                                  @@ -815,17 +869,23 @@

                                  Show Answers:

                                  -
                                  -
                                  +
                                  +
                                  - - Answers will be shown after the number of attempts has been met + +
                                  + + Answers will be shown after the number of attempts has been met +
                                  -
                                  +
                                  - - Answers will never be shown, regardless of attempts + +
                                  + + Answers will never be shown, regardless of attempts +
                                  @@ -855,17 +915,23 @@

                                  Anonymous Discussions:

                                  -
                                  -
                                  +
                                  +
                                  - - Students and faculty will be able to post anonymously + +
                                  + + Students and faculty will be able to post anonymously +
                                  -
                                  +
                                  - - Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous + +
                                  + + Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous +
                                  From 278c731aefbdfd363ac5758360d6b345f473df25 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 20 Nov 2012 09:26:40 -0500 Subject: [PATCH 041/243] settings - made assignment types user-determined fields vs. pre-set types in UI --- cms/static/sass/_settings.scss | 17 +- cms/templates/settings.html | 369 ++++++++++----------------------- 2 files changed, 118 insertions(+), 268 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 855f9f6009..dafd51e9f9 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -83,7 +83,7 @@ .detail { float: right; - marign-top: 3px; + margin-top: 3px; color: $mediumGrey; font-size: 13px; } @@ -231,7 +231,7 @@ &.multi { display: block; background: tint($lightGrey, 50%); - padding: 15px; + padding: 20px 15px; @include border-radius(4px); @include box-sizing(border-box); @@ -298,6 +298,9 @@ &:last-child { border: 0; } + + .row { + } } } @@ -481,9 +484,15 @@ .settings-grading { - .course-grading-gradeweight, .course-grading-totalassignments, .course-grading-totalassignmentsdroppable { + .input-list { + .row { - input { + .input { + &:last-child { + margin-bottom: 0; + padding-bottom: 0; + } + } } } } diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 8ac9460f62..57fdeb34d5 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -167,7 +167,7 @@

                                  Course Schedule

                                  - Important steps and segments of your your course + Important steps and segments of your course
                                  @@ -460,8 +460,8 @@
                                  - Boston, MA Local Time (UTC/GMT -5 hours) - Convert to your time zone + Boston, MA Local Time (UTC/GMT -5 hours).
                                  + Convert to your time zone
                                  @@ -478,122 +478,114 @@
                                  -
                                  +
                                  -

                                  Lesson Exercises

                                  - Grading in-lesson question & problems +

                                  Assignment Types

                                  -
                                  - +
                                  +
                                  +
                                    +
                                  • +
                                    + -
                                    -
                                    - - e.g. 25% -
                                    -
                                    -
                                    +
                                    +
                                    + + e.g. Homework, Labs, Midterm Exams, Final Exam +
                                    +
                                    +
                                  -
                                  - +
                                  + -
                                  -
                                  - - total exercises assigned -
                                  -
                                  -
                                  +
                                  +
                                  + + e.g. 25% +
                                  +
                                  +
                                  -
                                  - +
                                  + -
                                  -
                                  - - total exercises that won't be graded -
                                  -
                                  -
                                  -
                                  +
                                  +
                                  + + total exercises assigned +
                                  +
                                  +
                                  -
                                  -
                                  -

                                  Labs

                                  - Grading in-lesson question & problems -
                                  +
                                  + -
                                  - +
                                  +
                                  + + total exercises that won't be graded +
                                  +
                                  +
                                  -
                                  -
                                  - - e.g. 25% -
                                  -
                                  -
                                  + Delete Assignment Type +
                                • -
                                  - +
                                • +
                                  + -
                                  -
                                  - - total labs assigned -
                                  -
                                  -
                                  +
                                  +
                                  + + e.g. Homework, Labs, Midterm Exams, Final Exam +
                                  +
                                  +
                                • -
                                  - +
                                  + -
                                  -
                                  - - total labs that won't be graded -
                                  -
                                  -
                                  - +
                                  +
                                  + + e.g. 25% +
                                  +
                                  +
                                  -
                                  -
                                  -

                                  Exams

                                  - Grading in-lesson question & problems -
                                  +
                                  + -
                                  - +
                                  +
                                  + + total exercises assigned +
                                  +
                                  +
                                  -
                                  -
                                  - - e.g. 50% -
                                  -
                                  -
                                  +
                                  + -
                                  - +
                                  +
                                  + + total exercises that won't be graded +
                                  +
                                  +
                                  -
                                  -
                                  - - total exams held -
                                  -
                                  -
                                  + Delete Assignment Type + +
                                -
                                - - -
                                -
                                - - total exams that won't be graded -
                                + + New Assignment Type +
                                @@ -677,10 +669,9 @@
                                -
                                +
                                -

                                Lesson Exercises

                                - In-lesson question & problem specific rules +

                                [Assignment Type Name]

                                @@ -688,28 +679,28 @@
                                - +
                                - + randomize all problems
                                - +
                                - + do not randomize problems
                                - +
                                - + randomize problems per student
                                @@ -721,19 +712,19 @@
                                - +
                                - + Answers will be shown after the number of attempts has been met
                                - +
                                - + Answers will never be shown, regardless of attempts
                                @@ -741,166 +732,16 @@
                                - +
                                - + To set infinite atttempts, use "0"
                                -
                                - -
                                -
                                -

                                Labs

                                - Exploratory assignment-specific rules -
                                - -
                                -

                                Problem Randomization:

                                - -
                                -
                                - - -
                                - - randomize all problems -
                                -
                                - -
                                - - -
                                - - do not randomize problems -
                                -
                                - -
                                - - -
                                - - randomize problems per student -
                                -
                                -
                                -
                                - -
                                -

                                Show Answers:

                                - -
                                -
                                - - -
                                - - Answers will be shown after the number of attempts has been met -
                                -
                                - -
                                - - -
                                - - Answers will never be shown, regardless of attempts -
                                -
                                -
                                -
                                - -
                                - - -
                                -
                                - - To set infinite atttempts, use "0" -
                                -
                                -
                                -
                                - -
                                -
                                -

                                Exams

                                - Mid-term and final exam-specific rules -
                                - -
                                -

                                Problem Randomization:

                                - -
                                -
                                - - -
                                - - randomize all problems -
                                -
                                - -
                                - - -
                                - - do not randomize problems -
                                -
                                - -
                                - - -
                                - - randomize problems per student -
                                -
                                -
                                -
                                - -
                                -

                                Show Answers:

                                - -
                                -
                                - - -
                                - - Answers will be shown after the number of attempts has been met -
                                -
                                - -
                                - - -
                                - - Answers will never be shown, regardless of attempts -
                                -
                                -
                                -
                                - -
                                - - -
                                -
                                - - To set infinite atttempts, use "0" -
                                -
                                -
                                -
                                +
                                From a69d3cb0644d9e105ea5f807d37a155029bbcc4f Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 20 Nov 2012 09:32:13 -0500 Subject: [PATCH 042/243] settings - discussion UI revision --- cms/static/sass/_settings.scss | 2 +- cms/templates/settings.html | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index dafd51e9f9..300a81f9dd 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -513,7 +513,7 @@ .settings-discussions { - .settings-discussions-categories .course-discussions-categories-list-item { + .course-discussions-categories-list-item { label { display: none; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 57fdeb34d5..db1c1393ef 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -775,9 +775,8 @@
                                - +
                                -

                                Discussion Categories

                                @@ -837,8 +836,7 @@
                                -
                                - +
                                From 6c3c3776195596fa07ba024506cba7315ab1e5cd Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 20 Nov 2012 11:34:16 -0500 Subject: [PATCH 043/243] settings - general class/ID naming convention cleaning --- cms/static/sass/_settings.scss | 8 ++ cms/templates/settings.html | 184 ++++++++++++++++----------------- 2 files changed, 100 insertions(+), 92 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 300a81f9dd..4e2490beaf 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -483,6 +483,14 @@ .settings-grading { + .course-grading-assignment-list-item { + + .row:nth-child(4) { + border: none; + margin-bottom: 0; + padding-bottom: 0; + } + } .input-list { .row { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index db1c1393ef..4ee64120f6 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -131,30 +131,30 @@
                                - +
                                - + This is used in your course URL, and cannot be changed
                                - +
                                - + This is used in your course URL, and cannot be changed
                                - +
                                - + e.g. 101x This is used in your course URL, and cannot be changed
                                @@ -176,14 +176,14 @@
                                - - + + First day the course begins
                                - - + + Last day the course is active
                                @@ -196,14 +196,14 @@
                                - - + + First day students can enroll
                                - - + + Last day students can enroll
                                @@ -211,18 +211,18 @@
                                - +
                                - + Upload Syllabus PDF formatting preferred @@ -304,24 +304,24 @@
                                • - +
                                  - +
                                  - +
                                  - +
                                  - +
                                  -
                                  + @@ -329,10 +329,10 @@
                                  -
                                  - +
                                  +
                                  - + A brief description of your education, experience, and expertise
                                  @@ -342,24 +342,24 @@
                                • - +
                                  - +
                                  - +
                                  - +
                                  - +
                                  - + Upload Faculty Photo Max size: 30KB @@ -367,11 +367,11 @@
                                  -
                                  - +
                                  +
                                  - + A brief description of your education, experience, and expertise
                                  @@ -399,7 +399,7 @@
                                  -
                                  +
                                  @@ -455,11 +455,11 @@
                                  - +
                                  - + Boston, MA Local Time (UTC/GMT -5 hours).
                                  Convert to your time zone
                                  @@ -471,7 +471,7 @@
                                  - + leeway on due dates
                                  @@ -488,11 +488,11 @@
                                  • - +
                                    - + e.g. Homework, Labs, Midterm Exams, Final Exam
                                    @@ -521,11 +521,11 @@
                                    - +
                                    -
                                    - +
                                    + total exercises that won't be graded
                                    @@ -536,11 +536,11 @@
                                  • - +
                                    - + e.g. Homework, Labs, Midterm Exams, Final Exam
                                    @@ -569,11 +569,11 @@
                                    - +
                                    -
                                    - +
                                    + total exercises that won't be graded
                                    @@ -605,28 +605,28 @@
                                    - +
                                    - + randomize all problems
                                    - +
                                    - + do not randomize problems
                                    - +
                                    - + randomize problems per student
                                    @@ -638,19 +638,19 @@
                                    - +
                                    - + Answers will be shown after the number of attempts has been met
                                    - +
                                    - + Answers will never be shown, regardless of attempts
                                    @@ -658,18 +658,18 @@
                                    - +
                                    - - To set infinite atttempts, use "0" + + Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"
                                    -
                                    +

                                    [Assignment Type Name]

                                    @@ -679,28 +679,28 @@
                                    - +
                                    - + randomize all problems
                                    - +
                                    - + do not randomize problems
                                    - +
                                    - + randomize problems per student
                                    @@ -712,19 +712,19 @@
                                    - +
                                    - + Answers will be shown after the number of attempts has been met
                                    - +
                                    - + Answers will never be shown, regardless of attempts
                                    @@ -732,12 +732,12 @@
                                    - +
                                    - - To set infinite atttempts, use "0" + + Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"
                                    @@ -758,19 +758,19 @@
                                    - +
                                    - + Students and faculty will be able to post anonymously
                                    - +
                                    - + Posting anonymously is not allowed. Any previous anonymous posts will be reverted to non-anonymous
                                    @@ -784,29 +784,29 @@
                                    • - - + +
                                    • - - + +
                                    • - - + +
                                    • - - + + Delete Category
                                      @@ -814,8 +814,8 @@
                                    • - - + +
                                      Delete Category @@ -823,8 +823,8 @@
                                    • - - + +
                                      Delete Category From 339949f934da5b7079730717e09a5faff98a4771 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Tue, 20 Nov 2012 11:37:14 -0500 Subject: [PATCH 044/243] Another checkpoint before pull/merge --- cms/static/js/models/course_relative.js | 4 +- .../js/models/settings/course_detais.js | 26 +---- .../js/models/settings/course_settings.js | 15 ++- .../js/views/settings/main_settings_view.js | 100 +++++++++++++++--- cms/templates/settings.html | 10 +- common/djangoapps/__init__.py | 0 .../models/settings/course_details.py | 20 +--- 7 files changed, 118 insertions(+), 57 deletions(-) create mode 100644 common/djangoapps/__init__.py diff --git a/cms/static/js/models/course_relative.js b/cms/static/js/models/course_relative.js index c9f20d6789..5c42f4ce80 100644 --- a/cms/static/js/models/course_relative.js +++ b/cms/static/js/models/course_relative.js @@ -1,7 +1,7 @@ CMS.Models.Location = Backbone.Models.extend({ defaults: { tag: "", - name: "", + org: "", course: "", category: "", name: "" @@ -9,7 +9,7 @@ CMS.Models.Location = Backbone.Models.extend({ toUrl: function(overrides) { return (overrides['tag'] ? overrides['tag'] : this.get('tag')) + "://" + - (overrides['name'] ? overrides['name'] : this.get('name')) + "/" + + (overrides['org'] ? overrides['org'] : this.get('org')) + "/" + (overrides['course'] ? overrides['course'] : this.get('course')) + "/" + (overrides['category'] ? overrides['category'] : this.get('category')) + "/" + (overrides['name'] ? overrides['name'] : this.get('name')) + "/"; diff --git a/cms/static/js/models/settings/course_detais.js b/cms/static/js/models/settings/course_detais.js index aa1fd1463d..8869280792 100644 --- a/cms/static/js/models/settings/course_detais.js +++ b/cms/static/js/models/settings/course_detais.js @@ -1,18 +1,14 @@ CMS.Models.Settings.CourseDetails = Backbone.Models.extend({ defaults: { location : null, # a Location model, required - start_date: null, - end_date: null, - milestones: null, # a CourseRelativeCollection + start_date: null, # maps to 'start' + end_date: null, # maps to 'end' + enrollment_start: null, + enrollment_end: null, syllabus: null, overview: "", - statement: "", intro_video: null, - requirements: "", - effort: null, # an int or null - textbooks: null, # a CourseRelativeCollection - prereqs: null, # a CourseRelativeCollection - faqs: null # a CourseRelativeCollection + effort: null # an int or null }, // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) @@ -20,18 +16,6 @@ CMS.Models.Settings.CourseDetails = Backbone.Models.extend({ if (attributes['location']) { attributes.location = new CMS.Models.Location(attributes.location); }; - if (attributes['milestones']) { - attributes.milestones = new CMS.Models.CourseRelativeCollection(attributes.milestones); - }; - if (attributes['textbooks']) { - attributes.textbooks = new CMS.Models.CourseRelativeCollection(attributes.textbooks); - }; - if (attributes['prereqs']) { - attributes.prereqs = new CMS.Models.CourseRelativeCollection(attributes.prereqs); - }; - if (attributes['faqs']) { - attributes.faqs = new CMS.Models.CourseRelativeCollection(attributes.faqs); - }; }, urlRoot: function() { diff --git a/cms/static/js/models/settings/course_settings.js b/cms/static/js/models/settings/course_settings.js index 10a8f4df69..c6959a8693 100644 --- a/cms/static/js/models/settings/course_settings.js +++ b/cms/static/js/models/settings/course_settings.js @@ -8,6 +8,19 @@ CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ grading: null, problems: null, discussions: null + }, + + retrieve: function(submodel, callback) { + if (this.get(submodel)) callback(); + else switch (submodel) { + case 'details': + this.set('details', new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')})).fetch({ + success : callback + }); + break; + + default: + break; + } } - // write getters which get the relevant sub model from the server if not already loaded }) \ No newline at end of file diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js index d910f4dbc0..407bb1d4a6 100644 --- a/cms/static/js/views/settings/main_settings_view.js +++ b/cms/static/js/views/settings/main_settings_view.js @@ -2,17 +2,61 @@ CMS.Views.Settings.Main = Backbone.View.extend({ // Model class is CMS.Models.Settings.CourseSettings // allow navigation between the tabs events: { - 'click .settings-page-menu a': "showSettingsTab" - }, - initialize: function() { - // load templates - }, - render: function() { - // create any necessary subviews and put them onto the page + 'click .settings-page-menu a': "showSettingsTab", + 'blur input' : 'updateModel' }, currentTab: null, + subviews: {}, # indexed by tab name + + initialize: function() { + // load templates + this.currentTab = this.$el.find('.settings-page-menu .is-shown').attr('data-section'); + // create the initial subview + this.subviews[this.currentTab] = this.createSubview(); + + // fill in fields + this.$el.find("#course-name-input").val(this.model.get('courseLocation').get('name')); + this.$el.find("#course-organization-input").val(this.model.get('courseLocation').get('org')); + this.$el.find("#course-number-input").val(this.model.get('courseLocation').get('course')); + this.render(); + }, + + render: function() { + // create any necessary subviews and put them onto the page + if (!this.model.has(this.currentTab)) { + // TODO disable screen until fetch completes? + this.model.retrieve(this.currentTab, function() { + this.subviews[this.currentTab] = this.createSubview(); + this.subviews[this.currentTab].render(); + }); + } + } + else this.callRenderFunction(); + + return this; + }, + + createSubview: function() { + switch (this.currentTab) { + case 'details': + return new CMS.Views.Settings.Details({ + el: this.$el.find('.settings-' + this.currentTab), + model: this.model.get(this.currentTab); + }); + break; + case 'faculty': + break; + case 'grading': + break; + case 'problems': + break; + case 'discussions': + break; + } + }, + showSettingsTab: function(e) { this.currentTab = $(e.target).attr('data-section'); $('.settings-page-section > section').hide(); @@ -20,10 +64,42 @@ CMS.Views.Settings.Main = Backbone.View.extend({ $('.settings-page-menu .is-shown').removeClass('is-shown'); $(e.target).addClass('is-shown'); // fetch model for the tab if not loaded already - if (!this.model.has(this.currentTab)) { - // TODO disable screen until fetch completes? - this.model.retrieve(this.currentTab, function() { this.render(); }); - } + this.render(); } -}) \ No newline at end of file +}); + +CMS.Views.Settings.Details = Backbone.View.extend({ + // Model class is CMS.Models.Settings.CourseDetails + events : { + "blur input" : "updateModel" + }, + render: function() { + if (this.model.has('start_date')) this.$el.find('#course-start-date-input').datepicker('setDate', this.model.get('start_date')); + if (this.model.has('end_date')) this.$el.find('#course-end-date-input').datepicker('setDate', this.model.get('end_date')); + if (this.model.has('enrollment_start')) this.$el.find('#course-enrollment-start-input').datepicker('setDate', this.model.get('enrollment_start')); + if (this.model.has('enrollment_end')) this.$el.find('#course-enrollment-end-input').datepicker('setDate', this.model.get('enrollment_end')); + }, + updateModel: function(event) { + // figure out which field + switch (event.currentTarget.id) { + case 'course-start-date-input': + this.model.set('start_date', $(event.currentTarget).datepicker('getDate')); + break; + case 'course-end-date-input': + this.model.set('end_date', $(event.currentTarget).datepicker('getDate')); + break; + case 'course-enrollment-start-date-input': + this.model.set('enrollment_start', $(event.currentTarget).datepicker('getDate')); + break; + case 'course-enrollment-end-date-input': + this.model.set('enrollment_end', $(event.currentTarget).datepicker('getDate')); + break; + + default: + break; + } + // save the updated model + this.model.save(); + } +}); \ No newline at end of file diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 5d9fed9464..3cbfb6aa75 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -28,6 +28,7 @@ el: $('.main-wrapper'), model : settingsModel) }); + $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); editor.render(); }); @@ -56,7 +57,6 @@ $('.new-grade-button').bind('click', addNewGrade); $body.on('click', '.remove-button', removeGrade); - $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); })(); function addNewGrade(e) { @@ -146,7 +146,7 @@
                                      - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                                    @@ -156,7 +156,7 @@
                                    - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                                    @@ -167,7 +167,7 @@
                                    e.g. 101x - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                                    @@ -255,7 +255,7 @@
                                    - Introductions, prerequisites, FAQs that are used on your course summary page + Introductions, prerequisites, FAQs that are used on your course summary page
                                    diff --git a/common/djangoapps/__init__.py b/common/djangoapps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/models/settings/course_details.py b/common/djangoapps/models/settings/course_details.py index 95ad22efb9..c2b78a0a7b 100644 --- a/common/djangoapps/models/settings/course_details.py +++ b/common/djangoapps/models/settings/course_details.py @@ -1,21 +1,15 @@ -from common.djangoapps.models.course_relative import CourseRelativeMember - ### A basic question is whether to break the details into schedule, intro, requirements, and misc sub objects class CourseDetails: def __init__(self, location): self.course_location = location # a Location obj - self.start_date = None - self.end_date = None - self.milestones = [] + self.start_date = None # 'start' + self.end_date = None # 'end' + self.enrollment_start = None + self.enrollment_end = None self.syllabus = None # a pdf file asset self.overview = "" # html to render as the overview - self.statement = "" self.intro_video = None # a video pointer - self.requirements = "" # html self.effort = None # int hours/week - self.textbooks = [] # linked_asset - self.prereqs = [] # linked_asset - self.faqs = [] # summary_detail_pair @classmethod def fetch(cls, course_location): @@ -28,9 +22,3 @@ class CourseDetails: return course -class CourseMilestone(CourseRelativeMember): - def __init__(self, location, idx): - CourseRelativeMember.__init__(self, location, idx) - self.date = None - self.description = "" - From baf8b481c89ce7ccf2be44a9544f15be9166d466 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 21 Nov 2012 13:58:56 -0500 Subject: [PATCH 045/243] settings - added in drag and drop anchors - wip --- cms/templates/settings.html | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 4ee64120f6..841b880f76 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -787,6 +787,8 @@
                                  + +
                                • @@ -794,6 +796,8 @@
                                + +
                              • @@ -801,6 +805,8 @@
                              + +
                            8. @@ -810,6 +816,8 @@ Delete Category
                            9. + +
                            10. @@ -819,15 +827,19 @@
                            11. Delete Category + + -
                            12. +
                            13. - +
                              Delete Category + +
                            14. From f0c94a2c7e3afe67c08cae10791cbd7d843eb8ac Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 21 Nov 2012 15:17:18 -0500 Subject: [PATCH 046/243] Beginning test of details tab. --- cms/djangoapps/contentstore/views.py | 54 ++++++++- .../js/models/settings/course_detais.js | 9 +- .../js/views/settings/main_settings_view.js | 58 +++++++++- cms/templates/settings.html | 25 ++-- cms/templates/widgets/header.html | 1 + cms/urls.py | 5 +- .../models/settings/course_details.py | 108 +++++++++++++++++- 7 files changed, 229 insertions(+), 31 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 7ad349cf65..c810dc7df7 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -44,6 +44,8 @@ import sys import tarfile import time from contentstore import course_info_model +from models.settings.course_details import CourseDetails +from models.settings.course_details import CourseDetailsEncoder # to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' @@ -871,9 +873,6 @@ def edit_static(request, org, course, coursename): return render_to_response('edit-static-page.html', {}) -def settings(request, org, course, coursename): - return render_to_response('settings.html', {}) - def edit_tabs(request, org, course, coursename): location = ['i4x', org, course, 'course', coursename] course_item = modulestore().get_item(location) @@ -949,13 +948,58 @@ def course_info_updates(request, org, course, provided_id=None): if request.method == 'GET': return HttpResponse(json.dumps(course_info_model.get_course_updates(location)), mimetype="application/json") elif real_method == 'POST': - # new instance (unless django makes PUT a POST): updates are coming as POST. Not sure why. return HttpResponse(json.dumps(course_info_model.update_course_updates(location, request.POST, provided_id)), mimetype="application/json") elif real_method == 'PUT': return HttpResponse(json.dumps(course_info_model.update_course_updates(location, request.POST, provided_id)), mimetype="application/json") - elif real_method == 'DELETE': # coming as POST need to pull from Request Header X-HTTP-Method-Override DELETE + elif real_method == 'DELETE': return HttpResponse(json.dumps(course_info_model.delete_course_update(location, request.POST, provided_id)), mimetype="application/json") +@login_required +@ensure_csrf_cookie +def get_course_settings(request, org, course, name): + """ + Send models and views as well as html for editing the course settings to the client. + + org, course, name: Attributes of the Location for the item to edit + """ + location = ['i4x', org, course, 'course', name] + + # check that logged in user has permissions to this item + if not has_access(request.user, location): + raise PermissionDenied() + + course_module = modulestore().get_item(location) + + return render_to_response('settings.html', { + 'active_tab': 'settings-tab', + 'context_course': course_module, + 'course_details' : json.dumps(CourseDetails.fetch(location), cls=CourseDetailsEncoder) + }) + +@expect_json +@login_required +@ensure_csrf_cookie +def course_settings_updates(request, org, course, name, section): + """ + restful CRUD operations on course_info updates. This differs from get_course_settings by communicating purely + through json (not rendering any html) and handles section level operations rather than whole page. + + org, course: Attributes of the Location for the item to edit + section: one of details, faculty, grading, problems, discussions + """ + if section == 'details': + manager = CourseDetails + else: return + + if request.method == 'GET': + # Cannot just do a get w/o knowing the course name :-( + return HttpResponse(json.dumps(manager.fetch(Location(['i4x', org, course, 'course',name])), cls=CourseDetailsEncoder), + mimetype="application/json") + elif request.method == 'POST': # post or put, doesn't matter. + return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseDetailsEncoder), + mimetype="application/json") + + @login_required @ensure_csrf_cookie def asset_index(request, org, course, name): diff --git a/cms/static/js/models/settings/course_detais.js b/cms/static/js/models/settings/course_detais.js index 8869280792..c725ed46a2 100644 --- a/cms/static/js/models/settings/course_detais.js +++ b/cms/static/js/models/settings/course_detais.js @@ -1,8 +1,8 @@ CMS.Models.Settings.CourseDetails = Backbone.Models.extend({ defaults: { - location : null, # a Location model, required - start_date: null, # maps to 'start' - end_date: null, # maps to 'end' + location : null, // the course's Location model, required + start_date: null, // maps to 'start' + end_date: null, // maps to 'end' enrollment_start: null, enrollment_end: null, syllabus: null, @@ -19,6 +19,7 @@ CMS.Models.Settings.CourseDetails = Backbone.Models.extend({ }, urlRoot: function() { - // TODO impl + var location = this.get('location'); + return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; } }); diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js index 91bc7134a4..5a862dd5ef 100644 --- a/cms/static/js/views/settings/main_settings_view.js +++ b/cms/static/js/views/settings/main_settings_view.js @@ -3,7 +3,6 @@ CMS.Views.Settings.Main = Backbone.View.extend({ // allow navigation between the tabs events: { 'click .settings-page-menu a': "showSettingsTab", - 'blur input' : 'updateModel' }, currentTab: null, @@ -72,19 +71,50 @@ CMS.Views.Settings.Main = Backbone.View.extend({ CMS.Views.Settings.Details = Backbone.View.extend({ // Model class is CMS.Models.Settings.CourseDetails events : { - "blur input" : "updateModel" + "blur input" : "updateModel", + 'click .remove-course-syllabus' : "removeSyllabus", + 'click .new-course-syllabus' : 'assetSyllabus', + 'click .remove-course-introduction-video' : "removeVideo", + 'click .new-course-introduction-video' : 'assetVideo', }, + initialize : function() { + // TODO move the html frag to a loaded asset + this.fileAnchorTemplate = _.template(' 📄<%= filename %>'); + // Save every change as it occurs. This may be too noisy!!! If not every change, then need sophisticated logic. + this.model.on('change', this.model.save); + }, + render: function() { if (this.model.has('start_date')) this.$el.find('#course-start-date').datepicker('setDate', this.model.get('start_date')); if (this.model.has('end_date')) this.$el.find('#course-end-date').datepicker('setDate', this.model.get('end_date')); if (this.model.has('enrollment_start')) this.$el.find('#course-enrollment-start').datepicker('setDate', this.model.get('enrollment_start')); if (this.model.has('enrollment_end')) this.$el.find('#course-enrollment-end').datepicker('setDate', this.model.get('enrollment_end')); + + if (this.model.has('syllabus')) { + this.$el.find('.current-course-syllabus .doc-filename').html( + this.fileAnchorTemplate({ + fullpath : this.model.get('syllabus'), + filename: 'syllabus'})); + this.$el.find('.remove-course-syllabus').show(); + } + else this.$el.find('.remove-course-syllabus').hide(); + + if (this.model.has('overview')) + this.$el.find('#course-overview').text(this.model.get('overview')); + + if (this.model.has('intro_video')) { + this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.get('intro_video')); + this.$el.find('.remove-course-introduction-video').show(); + } + else this.$el.find('.remove-course-introduction-video').hide(); }, + updateModel: function(event) { // figure out which field switch (event.currentTarget.id) { case 'course-start-date': - this.model.set('start_date', $(event.currentTarget).datepicker('getDate')); + var val = $(event.currentTarget).datepicker('getDate'); + this.model.set('start_date', val); break; case 'course-end-date': this.model.set('end_date', $(event.currentTarget).datepicker('getDate')); @@ -96,10 +126,28 @@ CMS.Views.Settings.Details = Backbone.View.extend({ this.model.set('enrollment_end', $(event.currentTarget).datepicker('getDate')); break; + case 'course-overview': + this.model.set('overview', $(event.currentTarget).text()); + break; + default: break; } - // save the updated model - this.model.save(); + }, + + removeSyllabus: function() { + if (this.model.has('syllabus')) this.model.set({'syllabus': null}); + }, + + assetSyllabus : function() { + // TODO implement + }, + + removeVideo: function() { + if (this.model.has('intro_video')) this.model.set({'intro_video': null}); + }, + + assetVideo : function() { + // TODO implement } }); \ No newline at end of file diff --git a/cms/templates/settings.html b/cms/templates/settings.html index cb1bebeb89..fc58674847 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -28,8 +28,14 @@ el: $('.main-wrapper'), model : settingsModel) }); - $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); + $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); + + $(":input, textarea").focus(function() { + $("label[for='" + this.id + "']").addClass("is-focused"); + }).blur(function() { + $("label").removeClass("is-focused"); + }); editor.render(); }); @@ -42,11 +48,6 @@ var gradeThresholds; var GRADES = ['A', 'B', 'C', 'D', 'E']; - $(" :input, textarea").focus(function() { - $("label[for='" + this.id + "']").addClass("is-focused"); - }).blur(function() { - $("label").removeClass("is-focused"); - }); (function() { $body = $('body'); @@ -146,7 +147,7 @@
                              - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                              @@ -225,8 +226,8 @@
                              -
                              - 📄CS184x_syllabus.pdf + @@ -255,7 +256,7 @@
                              - Introductions, prerequisites, FAQs that are used on your course summary page + Introductions, prerequisites, FAQs that are used on your course summary page
                              @@ -264,8 +265,8 @@
                              -
                              - + diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 73ce3f0604..f65becb9c7 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -14,6 +14,7 @@
                            15. Tabs
                            16. Assets
                            17. Users
                            18. +
                            19. Settings
                            20. Import
                            21. % endif diff --git a/cms/urls.py b/cms/urls.py index 7fc2a152a3..9cfae48865 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -36,14 +36,13 @@ urlpatterns = ('', 'contentstore.views.remove_user', name='remove_user'), url(r'^(?P[^/]+)/(?P[^/]+)/info/(?P[^/]+)$', 'contentstore.views.course_info', name='course_info'), url(r'^(?P[^/]+)/(?P[^/]+)/course_info/updates/(?P.*)$', 'contentstore.views.course_info_updates', name='course_info'), - url(r'^(?P[^/]+)/(?P[^/]+)/settings/(?P[^/]+)$', 'contentstore.views.course_settings', name='course_settings'), - url(r'^(?P[^/]+)/(?P[^/]+)/course_settings/updates/(?P.*)$', 'contentstore.views.course_settings_updates', name='course_settings'), + url(r'^(?P[^/]+)/(?P[^/]+)/settings/(?P[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'), + url(r'^(?P[^/]+)/(?P[^/]+)/settings/(?P[^/]+)/section/(?P
                              [^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'), url(r'^pages/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.static_pages', name='static_pages'), url(r'^edit_static/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_static', name='edit_static'), url(r'^edit_tabs/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.edit_tabs', name='edit_tabs'), url(r'^(?P[^/]+)/(?P[^/]+)/assets/(?P[^/]+)$', 'contentstore.views.asset_index', name='asset_index'), - url(r'^settings/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.settings', name='settings'), # temporary landing page for a course url(r'^edge/(?P[^/]+)/(?P[^/]+)/course/(?P[^/]+)$', 'contentstore.views.landing', name='landing'), diff --git a/common/djangoapps/models/settings/course_details.py b/common/djangoapps/models/settings/course_details.py index c2b78a0a7b..ed28733283 100644 --- a/common/djangoapps/models/settings/course_details.py +++ b/common/djangoapps/models/settings/course_details.py @@ -1,4 +1,10 @@ -### A basic question is whether to break the details into schedule, intro, requirements, and misc sub objects +from xmodule.modulestore.django import modulestore +from xmodule.course_module import CourseDescriptor +from xmodule.modulestore import Location +from xmodule.modulestore.exceptions import ItemNotFoundError +import json +from json.encoder import JSONEncoder + class CourseDetails: def __init__(self, location): self.course_location = location # a Location obj @@ -16,9 +22,107 @@ class CourseDetails: """ Fetch the course details for the given course from persistence and return a CourseDetails model. """ + if not isinstance(course_location, Location): + course_location = Location(course_location) + course = cls(course_location) - # TODO implement + descriptor = modulestore('direct').get_item(course_location) + + ## DEBUG verify that this is a ClassDescriptor object + if not isinstance(descriptor, CourseDescriptor): + print("oops, not the expected type: ", descriptor) + + ## FIXME convert these from time.struct_time objects to something the client wants + course.start_date = descriptor.start + course.end_date = descriptor.end + course.enrollment_start = descriptor.enrollment_start + course.enrollment_end = descriptor.enrollment_end + + temploc = course_location._replace(category='about', name='syllabus') + try: + course.syllabus = modulestore('direct').get_item(temploc).definition['data'] + except ItemNotFoundError: + pass + + temploc = course_location._replace(name='overview') + try: + course.overview = modulestore('direct').get_item(temploc).definition['data'] + except ItemNotFoundError: + pass + + temploc = course_location._replace(name='effort') + try: + course.effort = modulestore('direct').get_item(temploc).definition['data'] + except ItemNotFoundError: + pass + + temploc = course_location._replace(name='video') + try: + course.intro_video = modulestore('direct').get_item(temploc).definition['data'] + except ItemNotFoundError: + pass return course + @classmethod + def update_from_json(cls, jsonval): + """ + Decode the json into CourseDetails and save any changed attrs to the db + """ + jsondict = json.loads(jsonval) + + ## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore + course_location = jsondict['course_location'] + ## Will probably want to cache the inflight courses because every blur generates an update + descriptor = modulestore('direct').get_item(course_location) + + dirty = False + + ## FIXME do more accurate comparison (convert to time? or convert persisted from time) + if (jsondict['start_date'] != descriptor.start): + dirty = True + descriptor.start = jsondict['start_date'] + + if (jsondict['end_date'] != descriptor.start): + dirty = True + descriptor.end = jsondict['end_date'] + + if (jsondict['enrollment_start'] != descriptor.enrollment_start): + dirty = True + descriptor.enrollment_start = jsondict['enrollment_start'] + + if (jsondict['enrollment_end'] != descriptor.enrollment_end): + dirty = True + descriptor.enrollment_end = jsondict['enrollment_end'] + + if dirty: + modulestore('direct').update_item(course_location, descriptor.definition['data']) + + # NOTE: below auto writes to the db w/o verifying that any of the fields actually changed + # to make faster, could compare against db or could have client send over a list of which fields changed. + temploc = course_location._replace(category='about', name='syllabus') + modulestore('direct').update_item(temploc, jsondict['syllabus']) + + temploc = course_location._replace(name='overview') + modulestore('direct').update_item(temploc, jsondict['overview']) + + temploc = course_location._replace(name='effort') + modulestore('direct').update_item(temploc, jsondict['effort']) + + temploc = course_location._replace(name='video') + modulestore('direct').update_item(temploc, jsondict['intro_video']) + + + # Could just generate and return a course obj w/o doing any db reads, but I put the reads in as a means to confirm + # it persisted correctly + return CourseDetails.fetch(course_location) + +class CourseDetailsEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, CourseDetails): + return obj.__dict__ + elif isinstance(obj, Location): + return obj.dict() + else: + return JSONEncoder.default(self, obj) \ No newline at end of file From ef95e1d95f9ed23123d7b033fc0d66ef7a436dfb Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 26 Nov 2012 11:48:56 -0500 Subject: [PATCH 047/243] settings - added in controls for sorting discussion categories --- cms/templates/settings.html | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 841b880f76..f1e77645e0 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -781,8 +781,8 @@

                              Discussion Categories

                              -
                                -
                              • +
                                  +
                                • @@ -791,7 +791,7 @@
                                • -
                                • +
                                • @@ -800,7 +800,7 @@
                                • -
                                • +
                                • @@ -809,7 +809,7 @@
                                • -
                                • +
                                • @@ -820,7 +820,7 @@
                                • -
                                • +
                                • @@ -831,10 +831,21 @@
                                • -
                                • +
                                • - + +
                                  + + Delete Category + + +
                                • + +
                                • +
                                  + +
                                  Delete Category From 4097bc1022b69b60455251d4ede910d3fd7a4d6f Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 26 Nov 2012 13:33:57 -0500 Subject: [PATCH 048/243] settings - revised grading slider UI cosmetics --- cms/static/sass/_settings.scss | 18 ++++++++++++++---- cms/templates/settings.html | 10 +++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 4e2490beaf..f36c9353c5 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -588,6 +588,14 @@ top: 1px; margin-right: 5px; } + + .well { + padding: 20px; + background: $lightGrey; + border: 1px solid $mediumGrey; + @include border-radius(4px); + @include box-shadow(0 1px 1px rgba(0,0,0,0.05) inset) + } } @@ -601,6 +609,7 @@ .grade-controls { @include clearfix; + width: 642px; } .new-grade-button { @@ -609,7 +618,7 @@ display: block; width: 29px; height: 29px; - margin: 4px 10px 0 0; + margin: 10px 20px 0 0; border-radius: 20px; border: 1px solid $darkGrey; @include linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); @@ -627,9 +636,9 @@ } .grade-slider { - float: right; - width: 95%; - height: 80px; + float: left; + width: 580px; + margin-bottom: 10px; .grade-bar { position: relative; @@ -702,6 +711,7 @@ top: 0; height: 50px; text-align: right; + @include border-radius(2px); &:hover, &.is-dragging { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index f1e77645e0..a0157a806a 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -399,7 +399,7 @@
                                  -
                                  +
                                  @@ -419,24 +419,24 @@
                                  1. A - 81-100 + 90-100 remove
                                  2. B - 71-80 + 80-89 remove
                                  3. C - 0-70 + 70-79 remove
                                  4. F - 0-50 + 0-69 remove
                                  5. From 4027576e157eee8e68edcbd330ba5164c722faff Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 26 Nov 2012 14:01:11 -0500 Subject: [PATCH 049/243] settings - resolved date and time picker UI bugs --- cms/static/sass/_settings.scss | 4 ++++ cms/templates/settings.html | 15 ++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index f36c9353c5..2918af01ed 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -135,7 +135,11 @@ } &.date { + display: block !important; + } + &.time { + display: block !important; } &:focus { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index a0157a806a..a1e480f7f0 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -177,13 +177,13 @@
                                    - + First day the course begins
                                    - + Last day the course is active
                                    @@ -197,13 +197,13 @@
                                    - + First day students can enroll
                                    - + Last day students can enroll
                                    @@ -458,10 +458,11 @@
                                    -
                                    - +
                                    + Boston, MA Local Time (UTC/GMT -5 hours).
                                    - Convert to your time zone
                                    + Convert to your time zone +
                                    From be848023a2b4f138eb7554f32f18654536836434 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 26 Nov 2012 14:21:01 -0500 Subject: [PATCH 050/243] settings - added in example of anon discussions disabled state --- cms/static/sass/_settings.scss | 4 ++++ cms/templates/settings.html | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index 2918af01ed..a71b9f199a 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -159,6 +159,10 @@ } + input:disabled + .copy > label, input:disabled + .label { + color: $mediumGrey; + } + .input-default input, .input-default textarea { color: $mediumGrey; diff --git a/cms/templates/settings.html b/cms/templates/settings.html index a1e480f7f0..37faa7c6a8 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -778,6 +778,30 @@
                                  +
                                  +

                                  Anonymous Discussions:

                                  + +
                                  +
                                  + + +
                                  + + Students and faculty will be able to post anonymously +
                                  +
                                  + +
                                  + + +
                                  + + This option is disabled since there are previous discussions that are anonymous. +
                                  +
                                  +
                                  +
                                  +

                                  Discussion Categories

                                  From bb2e78ce30587f3856dedc4c15cff153a939312a Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Mon, 26 Nov 2012 15:54:46 -0500 Subject: [PATCH 051/243] reworked slider logic --- cms/static/sass/_settings.scss | 7 ++-- cms/templates/settings.html | 72 ++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index a71b9f199a..b5dcaad9be 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -755,11 +755,12 @@ } &:nth-child(4) { - background: #fb336c; + background: #ef54a1; } - &:nth-child(5) { - background: #ef54a1; + &:nth-child(5), + &.bar-fail { + background: #fb336c; } .letter-grade { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 37faa7c6a8..8e8ac591d0 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -18,7 +18,7 @@ var barOrigin; var barWidth; var gradeThresholds; - var GRADES = ['A', 'B', 'C', 'D', 'E']; + var GRADES = ['A', 'B', 'C', 'D', 'F']; $(" :input, textarea").focus(function() { $("label[for='" + this.id + "']").addClass("is-focused"); @@ -29,20 +29,41 @@ (function() { $body = $('body'); $gradeBar = $('.grade-bar'); - gradeThresholds = [100, 80, 70]; + // gradeThresholds = [100, 50]; + setThresholds(); $('.settings-page-menu a').bind('click', showSettingsTab); $('.settings-extra header').bind('click', showSettingsExtras); $body.on('mousedown', '.drag-bar', startDragBar); $('.new-grade-button').bind('click', addNewGrade); $body.on('click', '.remove-button', removeGrade); + renderGradeRanges(); $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); })(); + function setThresholds() { + gradeThresholds = []; + $('.grades li').each(function(i) { + gradeThresholds.push(parseInt($(this)[0].style.width)); + }); + } + function addNewGrade(e) { e.preventDefault(); - var $newGradeBar = $('
                                • ' + GRADES[$('.grades li').length] + 'remove
                                • '); - $('.grades').append($newGradeBar); + var $grades = $('.grades'); + var gradeLength = $('li', $grades).length; + if(gradeLength > 4) { + return; + } + var $newGradeBar = $('
                                • ' + GRADES[gradeLength - 1] + 'remove
                                • '); + var failBarWidth = parseFloat($('.bar-fail', $grades)[0].style.width); + var lastBarWidth = parseFloat($('li', $grades).eq(gradeLength - 2)[0].style.width); + var targetWidth = failBarWidth + ((lastBarWidth - failBarWidth) / 2); + $newGradeBar.css('width', targetWidth + '%'); + $('.bar-fail', $grades).before($newGradeBar); + setGrades(); + setThresholds(); + renderGradeRanges(); } function removeGrade(e) { @@ -50,6 +71,23 @@ var index = $(this).closest('li').index(); gradeThresholds.splice(index, 1); $(this).closest('li').remove(); + setGrades(); + setThresholds(); + renderGradeRanges(); + } + + function setGrades() { + var $gradeBars = $('.grades li'); + var barCount = $gradeBars.length; + if(barCount <= 2) { + $gradeBars.eq(0).find('.letter-grade').html('Pass'); + $('.bar-fail').find('.letter-grade').html('Fail'); + } else { + $gradeBars.each(function(i) { + $('.letter-grade', this).html(GRADES[i]); + }); + $('.bar-fail').find('.letter-grade').html('F'); + } } function showSettingsTab(e) { @@ -121,7 +159,7 @@
                              -
                              +

                              Course Details

                              @@ -388,7 +426,7 @@
                              -
                              +

                              Grading

                              @@ -418,27 +456,13 @@
                              1. - A - 90-100 - remove -
                              2. -
                              3. - B - 80-89 - - remove -
                              4. -
                              5. - C - 70-79 - - remove + Pass +
                              6. - F - 0-69 + Fail + - remove
                              From 153285c3ea82ab54dda0a758b862604ad0cf4ab8 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Mon, 26 Nov 2012 16:28:52 -0500 Subject: [PATCH 052/243] reset default tab --- cms/templates/settings.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 8e8ac591d0..9e6225b878 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -159,7 +159,7 @@
                              -
                              +

                              Course Details

                              @@ -426,7 +426,7 @@
                              -
                              +

                              Grading

                              From c3e11b42172cef4c577a9303d3a6db7babed2e69 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 27 Nov 2012 09:50:25 -0500 Subject: [PATCH 053/243] settings - added in additional time fields for course dates --- cms/static/sass/_settings.scss | 6 +++++ cms/templates/settings.html | 42 ++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/cms/static/sass/_settings.scss b/cms/static/sass/_settings.scss index b5dcaad9be..ab72bc464c 100644 --- a/cms/static/sass/_settings.scss +++ b/cms/static/sass/_settings.scss @@ -140,6 +140,8 @@ &.time { display: block !important; + width: 75px !important; + min-width: 75px !important; } &:focus { @@ -372,6 +374,10 @@ } } } + + .field-additional { + margin-left: 204px; + } } // editing controls - adding diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 9e6225b878..b524b799fb 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -219,11 +219,27 @@ First day the course begins
                              +
                              + + + (UTC/GMT -5 hours) +
                              +
                              +
                              + +
                              +
                              Last day the course is active
                              + +
                              + + + (UTC/GMT -5 hours) +
                              @@ -239,10 +255,26 @@ First day students can enroll
                              +
                              + + + (UTC/GMT -5 hours) +
                              +
                              +
                              + +
                              +
                              - - - Last day students can enroll + + + First day students can enroll +
                              + +
                              + + + (UTC/GMT -5 hours)
                              @@ -320,7 +352,7 @@
                              - + Time students should spend on all course work
                              @@ -496,7 +528,7 @@
                              - + leeway on due dates
                              From 2238019539f0352512e68fbb1373de1bdd4acf83 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Tue, 27 Nov 2012 11:32:03 -0500 Subject: [PATCH 054/243] Details tab works except for file references --- cms/static/js/models/course_relative.js | 30 ++++---- .../js/models/settings/course_details.js | 40 +++++++++++ .../js/models/settings/course_detais.js | 25 ------- .../js/models/settings/course_settings.js | 1 + .../js/views/settings/main_settings_view.js | 64 ++++++++++------- cms/templates/settings.html | 16 +++-- .../models/settings/course_details.py | 68 ++++++++++++------- common/djangoapps/util/converters.py | 20 ++++++ common/lib/xmodule/xmodule/course_module.py | 28 ++++++-- common/lib/xmodule/xmodule/x_module.py | 8 ++- 10 files changed, 197 insertions(+), 103 deletions(-) create mode 100644 cms/static/js/models/settings/course_details.js delete mode 100644 cms/static/js/models/settings/course_detais.js create mode 100644 common/djangoapps/util/converters.py diff --git a/cms/static/js/models/course_relative.js b/cms/static/js/models/course_relative.js index 5c42f4ce80..c33339ff48 100644 --- a/cms/static/js/models/course_relative.js +++ b/cms/static/js/models/course_relative.js @@ -1,4 +1,4 @@ -CMS.Models.Location = Backbone.Models.extend({ +CMS.Models.Location = Backbone.Model.extend({ defaults: { tag: "", org: "", @@ -14,29 +14,29 @@ CMS.Models.Location = Backbone.Models.extend({ (overrides['category'] ? overrides['category'] : this.get('category')) + "/" + (overrides['name'] ? overrides['name'] : this.get('name')) + "/"; }, - _tagPattern = /[^:]+/g, - _fieldPattern = new RegExp('[^/]+','g'), + _tagPattern : /[^:]+/g, + _fieldPattern : new RegExp('[^/]+','g'), parse: function(payload) { - if (payload instanceof Array) { + if (_.isArray(payload)) { return { tag: payload[0], - name: payload[1], + org: payload[1], course: payload[2], category: payload[3], name: payload[4] } } - else if (payload instanceof String) { + else if (_.isString(payload)) { var foundTag = this._tagPattern.exec(payload); if (foundTag) { - this._fieldPattern.lastIndex = this._tagPattern.lastIndex; + this._fieldPattern.lastIndex = this._tagPattern.lastIndex + 1; // skip over the colon return { - tag: foundTag, - name: this._fieldPattern.exec(payload), - course: this._fieldPattern.exec(payload), - category: this._fieldPattern.exec(payload), - name: this._fieldPattern.exec(payload) + tag: foundTag[0], + org: this._fieldPattern.exec(payload)[0], + course: this._fieldPattern.exec(payload)[0], + category: this._fieldPattern.exec(payload)[0], + name: this._fieldPattern.exec(payload)[0] } } else return null; @@ -47,13 +47,13 @@ CMS.Models.Location = Backbone.Models.extend({ } }); -CMS.Models.CourseRelative = Backbone.Models.extend({ +CMS.Models.CourseRelative = Backbone.Model.extend({ defaults: { course_location : null, // must never be null, but here to doc the field idx : null // the index making it unique in the containing collection (no implied sort) } }); -CMS.Models.CourseRelativeCollection = Backbone.Collections.extend({ - model : CourseRelative +CMS.Models.CourseRelativeCollection = Backbone.Collection.extend({ + model : CMS.Models.CourseRelative }); \ No newline at end of file diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js new file mode 100644 index 0000000000..3ae4a86f90 --- /dev/null +++ b/cms/static/js/models/settings/course_details.js @@ -0,0 +1,40 @@ +if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); + +CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ + defaults: { + location : null, // the course's Location model, required + start_date: null, // maps to 'start' + end_date: null, // maps to 'end' + enrollment_start: null, + enrollment_end: null, + syllabus: null, + overview: "", + intro_video: null, + effort: null // an int or null + }, + + // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) + parse: function(attributes) { + if (attributes['course_location']) { + attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true}); + } + if (attributes['start_date']) { + attributes.start_date = new Date(attributes.start_date); + } + if (attributes['end_date']) { + attributes.end_date = new Date(attributes.end_date); + } + if (attributes['enrollment_start']) { + attributes.enrollment_start = new Date(attributes.enrollment_start); + } + if (attributes['enrollment_end']) { + attributes.enrollment_end = new Date(attributes.enrollment_end); + } + return attributes; + }, + + urlRoot: function() { + var location = this.get('location'); + return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; + } +}); diff --git a/cms/static/js/models/settings/course_detais.js b/cms/static/js/models/settings/course_detais.js deleted file mode 100644 index c725ed46a2..0000000000 --- a/cms/static/js/models/settings/course_detais.js +++ /dev/null @@ -1,25 +0,0 @@ -CMS.Models.Settings.CourseDetails = Backbone.Models.extend({ - defaults: { - location : null, // the course's Location model, required - start_date: null, // maps to 'start' - end_date: null, // maps to 'end' - enrollment_start: null, - enrollment_end: null, - syllabus: null, - overview: "", - intro_video: null, - effort: null # an int or null - }, - - // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) - parse: function(attributes) { - if (attributes['location']) { - attributes.location = new CMS.Models.Location(attributes.location); - }; - }, - - urlRoot: function() { - var location = this.get('location'); - return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; - } -}); diff --git a/cms/static/js/models/settings/course_settings.js b/cms/static/js/models/settings/course_settings.js index c6959a8693..de4468c00b 100644 --- a/cms/static/js/models/settings/course_settings.js +++ b/cms/static/js/models/settings/course_settings.js @@ -1,3 +1,4 @@ +if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ // a container for the models representing the n possible tabbed states defaults: { diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js index 5a862dd5ef..b4019e12ea 100644 --- a/cms/static/js/views/settings/main_settings_view.js +++ b/cms/static/js/views/settings/main_settings_view.js @@ -1,3 +1,5 @@ +if (!CMS.Views['Settings']) CMS.Views.Settings = new Object(); + CMS.Views.Settings.Main = Backbone.View.extend({ // Model class is CMS.Models.Settings.CourseSettings // allow navigation between the tabs @@ -6,7 +8,7 @@ CMS.Views.Settings.Main = Backbone.View.extend({ }, currentTab: null, - subviews: {}, # indexed by tab name + subviews: {}, // indexed by tab name initialize: function() { // load templates @@ -31,8 +33,7 @@ CMS.Views.Settings.Main = Backbone.View.extend({ this.subviews[this.currentTab].render(); }); } - } - else this.callRenderFunction(); + else this.subviews[this.currentTab].render(); return this; }, @@ -42,7 +43,7 @@ CMS.Views.Settings.Main = Backbone.View.extend({ case 'details': return new CMS.Views.Settings.Details({ el: this.$el.find('.settings-' + this.currentTab), - model: this.model.get(this.currentTab); + model: this.model.get(this.currentTab) }); break; case 'faculty': @@ -72,6 +73,7 @@ CMS.Views.Settings.Details = Backbone.View.extend({ // Model class is CMS.Models.Settings.CourseDetails events : { "blur input" : "updateModel", + "blur textarea" : "updateModel", 'click .remove-course-syllabus' : "removeSyllabus", 'click .new-course-syllabus' : 'assetSyllabus', 'click .remove-course-introduction-video' : "removeVideo", @@ -80,15 +82,13 @@ CMS.Views.Settings.Details = Backbone.View.extend({ initialize : function() { // TODO move the html frag to a loaded asset this.fileAnchorTemplate = _.template(' 📄<%= filename %>'); - // Save every change as it occurs. This may be too noisy!!! If not every change, then need sophisticated logic. - this.model.on('change', this.model.save); }, render: function() { - if (this.model.has('start_date')) this.$el.find('#course-start-date').datepicker('setDate', this.model.get('start_date')); - if (this.model.has('end_date')) this.$el.find('#course-end-date').datepicker('setDate', this.model.get('end_date')); - if (this.model.has('enrollment_start')) this.$el.find('#course-enrollment-start').datepicker('setDate', this.model.get('enrollment_start')); - if (this.model.has('enrollment_end')) this.$el.find('#course-enrollment-end').datepicker('setDate', this.model.get('enrollment_end')); + this.setupDatePicker('#course-start-date', 'start_date'); + this.setupDatePicker('#course-end-date', 'end_date'); + this.setupDatePicker('#course-enrollment-start-date', 'enrollment_start'); + this.setupDatePicker('#course-enrollment-end-date', 'enrollment_end'); if (this.model.has('syllabus')) { this.$el.find('.current-course-syllabus .doc-filename').html( @@ -97,46 +97,58 @@ CMS.Views.Settings.Details = Backbone.View.extend({ filename: 'syllabus'})); this.$el.find('.remove-course-syllabus').show(); } - else this.$el.find('.remove-course-syllabus').hide(); + else { + this.$el.find('.current-course-syllabus .doc-filename').html(""); + this.$el.find('.remove-course-syllabus').hide(); + } - if (this.model.has('overview')) - this.$el.find('#course-overview').text(this.model.get('overview')); + this.$el.find('#course-overview').val(this.model.get('overview')); + this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.get('intro_video')); if (this.model.has('intro_video')) { - this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.get('intro_video')); this.$el.find('.remove-course-introduction-video').show(); } else this.$el.find('.remove-course-introduction-video').hide(); + + this.$el.find("#course-effort").val(this.model.get('effort')); + + return this; + }, + + setupDatePicker : function(elementName, fieldName) { + var cacheModel = this.model; + var picker = this.$el.find(elementName); + picker.datepicker({ onSelect : function(newVal) { + cacheModel.save(fieldName, new Date(newVal)); + }}); + picker.datepicker('setDate', this.model.get(fieldName)); }, updateModel: function(event) { // figure out which field switch (event.currentTarget.id) { - case 'course-start-date': - var val = $(event.currentTarget).datepicker('getDate'); - this.model.set('start_date', val); - break; + case 'course-start-date': // handled via onSelect method case 'course-end-date': - this.model.set('end_date', $(event.currentTarget).datepicker('getDate')); - break; case 'course-enrollment-start-date': - this.model.set('enrollment_start', $(event.currentTarget).datepicker('getDate')); - break; case 'course-enrollment-end-date': - this.model.set('enrollment_end', $(event.currentTarget).datepicker('getDate')); break; case 'course-overview': - this.model.set('overview', $(event.currentTarget).text()); + this.model.save('overview', $(event.currentTarget).val()); break; + case 'course-effort': + this.model.save('effort', $(event.currentTarget).val()); + break; + default: break; } + }, removeSyllabus: function() { - if (this.model.has('syllabus')) this.model.set({'syllabus': null}); + if (this.model.has('syllabus')) this.model.save({'syllabus': null}); }, assetSyllabus : function() { @@ -144,7 +156,7 @@ CMS.Views.Settings.Details = Backbone.View.extend({ }, removeVideo: function() { - if (this.model.has('intro_video')) this.model.set({'intro_video': null}); + if (this.model.has('intro_video')) this.model.save({'intro_video': null}); }, assetVideo : function() { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index fc58674847..9d5514c90d 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -3,6 +3,10 @@ <%block name="title">Settings <%namespace name='static' file='static_content.html'/> +<%! +from contentstore import utils +%> + <%block name="jsextra"> @@ -24,9 +28,9 @@ details: new CMS.Models.Settings.CourseDetails(${course_details|n},{parse:true}) }); - var editor = new CMS.Views.CourseInfoEdit({ + var editor = new CMS.Views.Settings.Main({ el: $('.main-wrapper'), - model : settingsModel) + model : settingsModel }); $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); @@ -147,7 +151,7 @@
                              - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                              @@ -157,7 +161,7 @@
                              - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                              @@ -168,7 +172,7 @@
                              e.g. 101x - This is used in your course URL, and cannot be changed + This is used in your course URL, and cannot be changed
                              @@ -256,7 +260,7 @@
                              - Introductions, prerequisites, FAQs that are used on your course summary page + Introductions, prerequisites, FAQs that are used on your course summary page
                              diff --git a/common/djangoapps/models/settings/course_details.py b/common/djangoapps/models/settings/course_details.py index ed28733283..9dcdea4c16 100644 --- a/common/djangoapps/models/settings/course_details.py +++ b/common/djangoapps/models/settings/course_details.py @@ -4,6 +4,8 @@ from xmodule.modulestore import Location from xmodule.modulestore.exceptions import ItemNotFoundError import json from json.encoder import JSONEncoder +import time +from util.converters import time_to_date, jsdate_to_time class CourseDetails: def __init__(self, location): @@ -29,11 +31,6 @@ class CourseDetails: descriptor = modulestore('direct').get_item(course_location) - ## DEBUG verify that this is a ClassDescriptor object - if not isinstance(descriptor, CourseDescriptor): - print("oops, not the expected type: ", descriptor) - - ## FIXME convert these from time.struct_time objects to something the client wants course.start_date = descriptor.start course.end_date = descriptor.end course.enrollment_start = descriptor.enrollment_start @@ -45,19 +42,19 @@ class CourseDetails: except ItemNotFoundError: pass - temploc = course_location._replace(name='overview') + temploc = temploc._replace(name='overview') try: course.overview = modulestore('direct').get_item(temploc).definition['data'] except ItemNotFoundError: pass - temploc = course_location._replace(name='effort') + temploc = temploc._replace(name='effort') try: course.effort = modulestore('direct').get_item(temploc).definition['data'] except ItemNotFoundError: pass - temploc = course_location._replace(name='video') + temploc = temploc._replace(name='video') try: course.intro_video = modulestore('direct').get_item(temploc).definition['data'] except ItemNotFoundError: @@ -66,12 +63,10 @@ class CourseDetails: return course @classmethod - def update_from_json(cls, jsonval): + def update_from_json(cls, jsondict): """ Decode the json into CourseDetails and save any changed attrs to the db """ - jsondict = json.loads(jsonval) - ## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore course_location = jsondict['course_location'] ## Will probably want to cache the inflight courses because every blur generates an update @@ -79,38 +74,57 @@ class CourseDetails: dirty = False - ## FIXME do more accurate comparison (convert to time? or convert persisted from time) - if (jsondict['start_date'] != descriptor.start): + ## ??? Will this comparison work? + if 'start_date' in jsondict: + converted = jsdate_to_time(jsondict['start_date']) + else: + converted = None + if converted != descriptor.start: dirty = True - descriptor.start = jsondict['start_date'] + descriptor.start = converted - if (jsondict['end_date'] != descriptor.start): + if 'end_date' in jsondict: + converted = jsdate_to_time(jsondict['end_date']) + else: + converted = None + + if converted != descriptor.end: dirty = True - descriptor.end = jsondict['end_date'] + descriptor.end = converted - if (jsondict['enrollment_start'] != descriptor.enrollment_start): + if 'enrollment_start' in jsondict: + converted = jsdate_to_time(jsondict['enrollment_start']) + else: + converted = None + + if converted != descriptor.enrollment_start: dirty = True - descriptor.enrollment_start = jsondict['enrollment_start'] + descriptor.enrollment_start = converted - if (jsondict['enrollment_end'] != descriptor.enrollment_end): + if 'enrollment_end' in jsondict: + converted = jsdate_to_time(jsondict['enrollment_end']) + else: + converted = None + + if converted != descriptor.enrollment_end: dirty = True - descriptor.enrollment_end = jsondict['enrollment_end'] + descriptor.enrollment_end = converted if dirty: - modulestore('direct').update_item(course_location, descriptor.definition['data']) + modulestore('direct').update_metadata(course_location, descriptor.metadata) # NOTE: below auto writes to the db w/o verifying that any of the fields actually changed # to make faster, could compare against db or could have client send over a list of which fields changed. - temploc = course_location._replace(category='about', name='syllabus') + temploc = Location(course_location)._replace(category='about', name='syllabus') modulestore('direct').update_item(temploc, jsondict['syllabus']) - temploc = course_location._replace(name='overview') + temploc = temploc._replace(name='overview') modulestore('direct').update_item(temploc, jsondict['overview']) - temploc = course_location._replace(name='effort') + temploc = temploc._replace(name='effort') modulestore('direct').update_item(temploc, jsondict['effort']) - temploc = course_location._replace(name='video') + temploc = temploc._replace(name='video') modulestore('direct').update_item(temploc, jsondict['intro_video']) @@ -124,5 +138,7 @@ class CourseDetailsEncoder(json.JSONEncoder): return obj.__dict__ elif isinstance(obj, Location): return obj.dict() + elif isinstance(obj, time.struct_time): + return time_to_date(obj) else: - return JSONEncoder.default(self, obj) \ No newline at end of file + return JSONEncoder.default(self, obj) diff --git a/common/djangoapps/util/converters.py b/common/djangoapps/util/converters.py new file mode 100644 index 0000000000..200fab9766 --- /dev/null +++ b/common/djangoapps/util/converters.py @@ -0,0 +1,20 @@ +import time, datetime +import re + +def time_to_date(time_obj): + """ + Convert a time.time_struct to a true universal time (can pass to js Date constructor) + """ + return time.mktime(time_obj) * 1000 + +def jsdate_to_time(field): + """ + Convert a true universal time (msec since epoch) from a string to a time obj + """ + if field is None: + return field + elif isinstance(field, unicode): # iso format but ignores time zone assuming it's Z + d=datetime.datetime(*map(int, re.split('[^\d]', field)[:-1])) + return d.utctimetuple() + elif isinstance(field, int): + return time.gmtime(field / 1000) \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 5b19d57dce..58adaa2d8d 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -90,10 +90,6 @@ class CourseDescriptor(SequenceDescriptor): log.critical(msg) system.error_tracker(msg) - self.enrollment_start = self._try_parse_time("enrollment_start") - self.enrollment_end = self._try_parse_time("enrollment_end") - self.end = self._try_parse_time("end") - # NOTE: relies on the modulestore to call set_grading_policy() right after # init. (Modulestore is in charge of figuring out where to load the policy from) @@ -249,6 +245,30 @@ class CourseDescriptor(SequenceDescriptor): def has_started(self): return time.gmtime() > self.start + @property + def end(self): + return self._try_parse_time("end") + @end.setter + def end(self, value): + if isinstance(value, time.struct_time): + self.metadata['end'] = stringify_time(value) + @property + def enrollment_start(self): + return self._try_parse_time("enrollment_start") + + @enrollment_start.setter + def enrollment_start(self, value): + if isinstance(value, time.struct_time): + self.metadata['enrollment_start'] = stringify_time(value) + @property + def enrollment_end(self): + return self._try_parse_time("enrollment_end") + + @enrollment_end.setter + def enrollment_end(self, value): + if isinstance(value, time.struct_time): + self.metadata['enrollment_end'] = stringify_time(value) + @property def grader(self): return self._grading_policy['GRADER'] diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 7668c1b1d1..94bad60cc8 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -10,10 +10,11 @@ from collections import namedtuple from pkg_resources import resource_listdir, resource_string, resource_isdir from xmodule.modulestore import Location -from xmodule.timeparse import parse_time +from xmodule.timeparse import parse_time, stringify_time from xmodule.contentstore.content import StaticContent, XASSET_SRCREF_PREFIX from xmodule.modulestore.exceptions import ItemNotFoundError +import time log = logging.getLogger('mitx.' + __name__) @@ -481,6 +482,11 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): return None return self._try_parse_time('start') + @start.setter + def start(self, value): + if isinstance(value, time.struct_time): + self.metadata['start'] = stringify_time(value) + @property def own_metadata(self): """ From bffd4255625990786cd56d83e87a5c13a06b5371 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 27 Nov 2012 13:05:00 -0500 Subject: [PATCH 055/243] settings - revised date and time picker markup to use already set conventions with the .datepair class --- cms/templates/settings.html | 44 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index b524b799fb..29161f5ff8 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -37,8 +37,6 @@ $('.new-grade-button').bind('click', addNewGrade); $body.on('click', '.remove-button', removeGrade); renderGradeRanges(); - - $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); })(); function setThresholds() { @@ -211,33 +209,33 @@

                              Course Dates:

                              -
                              +
                              - + First day the course begins
                              -
                              +
                              - + (UTC/GMT -5 hours)
                              -
                              +
                              - + Last day the course is active
                              -
                              +
                              - + (UTC/GMT -5 hours)
                              @@ -247,33 +245,33 @@

                              Enrollment Dates:

                              -
                              +
                              - + First day students can enroll
                              -
                              +
                              - + (UTC/GMT -5 hours)
                              -
                              +
                              - + First day students can enroll
                              -
                              +
                              - + (UTC/GMT -5 hours)
                              @@ -353,7 +351,7 @@
                              - Time students should spend on all course work + Time students should spend on all course work
                              @@ -513,9 +511,9 @@
                              -
                              -
                              - +
                              +
                              + Boston, MA Local Time (UTC/GMT -5 hours).
                              Convert to your time zone
                              @@ -529,7 +527,7 @@
                              - leeway on due dates + leeway on due dates
                              From 5e2dba6bada2d22da725091211ea914e80f80081 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Tue, 27 Nov 2012 13:22:44 -0500 Subject: [PATCH 056/243] Minor cache to pull talbott's changes --- cms/static/js/views/settings/main_settings_view.js | 6 ++++++ cms/templates/settings.html | 14 -------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js index b4019e12ea..d957024e7d 100644 --- a/cms/static/js/views/settings/main_settings_view.js +++ b/cms/static/js/views/settings/main_settings_view.js @@ -20,6 +20,12 @@ CMS.Views.Settings.Main = Backbone.View.extend({ this.$el.find("#course-name").val(this.model.get('courseLocation').get('name')); this.$el.find("#course-organization").val(this.model.get('courseLocation').get('org')); this.$el.find("#course-number").val(this.model.get('courseLocation').get('course')); + this.$el.find('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); + this.$el.find(":input, textarea").focus(function() { + $("label[for='" + this.id + "']").addClass("is-focused"); + }).blur(function() { + $("label").removeClass("is-focused"); + }); this.render(); }, diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 47bbe23e21..d49db21598 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -33,13 +33,6 @@ from contentstore import utils model : settingsModel }); - $('.set-date').datepicker({ 'dateFormat': 'm/d/yy' }); - - $(":input, textarea").focus(function() { - $("label[for='" + this.id + "']").addClass("is-focused"); - }).blur(function() { - $("label").removeClass("is-focused"); - }); editor.render(); }); @@ -52,13 +45,6 @@ from contentstore import utils var gradeThresholds; var GRADES = ['A', 'B', 'C', 'D', 'F']; - // FIXME move into view class - $(" :input, textarea").focus(function() { - $("label[for='" + this.id + "']").addClass("is-focused"); - }).blur(function() { - $("label").removeClass("is-focused"); - }); - (function() { $body = $('body'); $gradeBar = $('.grade-bar'); From 075bc75a7d04a18156f3c43cb37e0586049f5928 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 27 Nov 2012 15:00:19 -0500 Subject: [PATCH 057/243] settings - adjusted markup to leverage Timepicker's datepair functionality for validating start/end dates --- cms/templates/settings.html | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 29161f5ff8..f3ee6badad 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -206,72 +206,72 @@ Important steps and segments of your course -
                              +

                              Course Dates:

                              -
                              +
                              - + First day the course begins
                              - + (UTC/GMT -5 hours)
                              -
                              +
                              - + Last day the course is active
                              - + (UTC/GMT -5 hours)
                              -
                              +

                              Enrollment Dates:

                              -
                              +
                              - + First day students can enroll
                              - + (UTC/GMT -5 hours)
                              -
                              +
                              - - - First day students can enroll + + + Last day students can enroll
                              - + (UTC/GMT -5 hours)
                              From 5331ae055fb1ae9e8714be12f8919d9db62df6e7 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Tue, 27 Nov 2012 15:15:28 -0500 Subject: [PATCH 058/243] Got time change hooked up but gained a spurious call to change on date change. --- .../js/views/settings/main_settings_view.js | 27 ++++++++++++------- cms/templates/settings.html | 8 +++--- .../models/settings/course_details.py | 3 +-- common/djangoapps/util/converters.py | 3 ++- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js index d957024e7d..a023f40232 100644 --- a/cms/static/js/views/settings/main_settings_view.js +++ b/cms/static/js/views/settings/main_settings_view.js @@ -91,10 +91,10 @@ CMS.Views.Settings.Details = Backbone.View.extend({ }, render: function() { - this.setupDatePicker('#course-start-date', 'start_date'); - this.setupDatePicker('#course-end-date', 'end_date'); - this.setupDatePicker('#course-enrollment-start-date', 'enrollment_start'); - this.setupDatePicker('#course-enrollment-end-date', 'enrollment_end'); + this.setupDatePicker('#course-start', 'start_date'); + this.setupDatePicker('#course-end', 'end_date'); + this.setupDatePicker('#enrollment-start', 'enrollment_start'); + this.setupDatePicker('#enrollment-end', 'enrollment_end'); if (this.model.has('syllabus')) { this.$el.find('.current-course-syllabus .doc-filename').html( @@ -123,11 +123,20 @@ CMS.Views.Settings.Details = Backbone.View.extend({ setupDatePicker : function(elementName, fieldName) { var cacheModel = this.model; - var picker = this.$el.find(elementName); - picker.datepicker({ onSelect : function(newVal) { - cacheModel.save(fieldName, new Date(newVal)); - }}); - picker.datepicker('setDate', this.model.get(fieldName)); + var div = this.$el.find(elementName); + var datefield = $(div).find(".date"); + var timefield = $(div).find(".time"); + var savefield = function(event) { + cacheModel.save(fieldName, new Date(datefield.datepicker('getDate').getTime() + + timefield.timepicker("getSecondsFromMidnight") * 1000)); + }; + + // FIXME being called 2x on each change. Was trapping datepicker onSelect b4 but change to datepair broke that + datefield.on('change', savefield); + timefield.on('changeTime', savefield); + + datefield.datepicker('setDate', this.model.get(fieldName)); + timefield.timepicker('setTime', this.model.get(fieldName)); }, updateModel: function(event) { diff --git a/cms/templates/settings.html b/cms/templates/settings.html index ddd4e09b0d..e9e70f6ca7 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -220,7 +220,7 @@ from contentstore import utils

                              Course Dates:

                              -
                              +
                              @@ -236,7 +236,7 @@ from contentstore import utils
                              -
                              +
                              @@ -256,7 +256,7 @@ from contentstore import utils

                              Enrollment Dates:

                              -
                              +
                              @@ -272,7 +272,7 @@ from contentstore import utils
                              -
                              +
                              diff --git a/common/djangoapps/models/settings/course_details.py b/common/djangoapps/models/settings/course_details.py index 9dcdea4c16..29820795f3 100644 --- a/common/djangoapps/models/settings/course_details.py +++ b/common/djangoapps/models/settings/course_details.py @@ -1,11 +1,10 @@ from xmodule.modulestore.django import modulestore -from xmodule.course_module import CourseDescriptor from xmodule.modulestore import Location from xmodule.modulestore.exceptions import ItemNotFoundError import json from json.encoder import JSONEncoder import time -from util.converters import time_to_date, jsdate_to_time +from util.converters import jsdate_to_time, time_to_date class CourseDetails: def __init__(self, location): diff --git a/common/djangoapps/util/converters.py b/common/djangoapps/util/converters.py index 200fab9766..6070338b55 100644 --- a/common/djangoapps/util/converters.py +++ b/common/djangoapps/util/converters.py @@ -1,11 +1,12 @@ import time, datetime import re +import calendar def time_to_date(time_obj): """ Convert a time.time_struct to a true universal time (can pass to js Date constructor) """ - return time.mktime(time_obj) * 1000 + return calendar.timegm(time_obj) * 1000 def jsdate_to_time(field): """ From a68dee9743b8ae8c4e0ed1ab513ee8eb28a11ff7 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Tue, 27 Nov 2012 16:38:21 -0500 Subject: [PATCH 059/243] Got rid of datepair as it was generating a jquery bug but also not doing what we wanted. --- cms/static/js/views/settings/main_settings_view.js | 7 +++++-- cms/templates/settings.html | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cms/static/js/views/settings/main_settings_view.js b/cms/static/js/views/settings/main_settings_view.js index a023f40232..b80cc3f48e 100644 --- a/cms/static/js/views/settings/main_settings_view.js +++ b/cms/static/js/views/settings/main_settings_view.js @@ -126,13 +126,16 @@ CMS.Views.Settings.Details = Backbone.View.extend({ var div = this.$el.find(elementName); var datefield = $(div).find(".date"); var timefield = $(div).find(".time"); - var savefield = function(event) { + var savefield = function() { cacheModel.save(fieldName, new Date(datefield.datepicker('getDate').getTime() + timefield.timepicker("getSecondsFromMidnight") * 1000)); }; + // instrument as date and time pickers + timefield.timepicker(); + // FIXME being called 2x on each change. Was trapping datepicker onSelect b4 but change to datepair broke that - datefield.on('change', savefield); + datefield.datepicker({ onSelect : savefield }); timefield.on('changeTime', savefield); datefield.datepicker('setDate', this.model.get(fieldName)); diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 8487c314e0..4d62bf6c1f 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -216,20 +216,20 @@ from contentstore import utils Important steps and segments of your course -
                              +

                              Course Dates:

                              - + First day the course begins
                              - + (UTC/GMT -5 hours)
                              @@ -252,7 +252,7 @@ from contentstore import utils
                              -
                              +

                              Enrollment Dates:

                              From a48a392eb2c9c686130461aacdeb91b06f2d9232 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 28 Nov 2012 10:02:01 -0500 Subject: [PATCH 060/243] Trying to get scss back --- .../contentstore/course_info_model.py | 13 ++++---- cms/djangoapps/contentstore/utils.py | 13 ++++++++ cms/djangoapps/contentstore/views.py | 31 +++++-------------- .../models/settings/course_details.py | 23 +++++++------- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py index 87dfa5da8f..ef60a0c1b3 100644 --- a/cms/djangoapps/contentstore/course_info_model.py +++ b/cms/djangoapps/contentstore/course_info_model.py @@ -4,6 +4,7 @@ from xmodule.modulestore.django import modulestore from lxml import etree import re from django.http import HttpResponseBadRequest +from contentstore.utils import get_modulestore ## TODO store as array of { date, content } and override course_info_module.definition_from_xml ## This should be in a class which inherits from XmlDescriptor @@ -13,10 +14,10 @@ def get_course_updates(location): [{id : location.url() + idx to make unique, date : string, content : html string}] """ try: - course_updates = modulestore('direct').get_item(location) + course_updates = get_modulestore(location).get_item(location) except ItemNotFoundError: template = Location(['i4x', 'edx', "templates", 'course_info', "Empty"]) - course_updates = modulestore('direct').clone_item(template, Location(location)) + course_updates = get_modulestore(location).clone_item(template, Location(location)) # current db rep: {"_id" : locationjson, "definition" : { "data" : "
                                [
                              1. date

                                content
                              2. ]
                              "} "metadata" : ignored} location_base = course_updates.location.url() @@ -53,7 +54,7 @@ def update_course_updates(location, update, passed_id=None): into the html structure. """ try: - course_updates = modulestore('direct').get_item(location) + course_updates = get_modulestore(location).get_item(location) except ItemNotFoundError: return HttpResponseBadRequest @@ -99,7 +100,7 @@ def update_course_updates(location, update, passed_id=None): # update db record course_updates.definition['data'] = etree.tostring(course_html_parsed) - modulestore('direct').update_item(location, course_updates.definition['data']) + get_modulestore(location).update_item(location, course_updates.definition['data']) return {"id" : passed_id, "date" : update['date'], @@ -114,7 +115,7 @@ def delete_course_update(location, update, passed_id): return HttpResponseBadRequest try: - course_updates = modulestore('direct').get_item(location) + course_updates = get_modulestore(location).get_item(location) except ItemNotFoundError: return HttpResponseBadRequest @@ -133,7 +134,7 @@ def delete_course_update(location, update, passed_id): # update db record course_updates.definition['data'] = etree.tostring(course_html_parsed) - store = modulestore('direct') + store = get_modulestore(location) store.update_item(location, course_updates.definition['data']) return get_course_updates(location) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 508236a1e9..62c46cc9d4 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -3,6 +3,19 @@ from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError +DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info'] + +def get_modulestore(location): + """ + Returns the correct modulestore to use for modifying the specified location + """ + if not isinstance(location, Location): + location = Location(location) + + if location.category in DIRECT_ONLY_CATEGORIES: + return modulestore('direct') + else: + return modulestore() def get_course_location_for_item(location): ''' diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index c810dc7df7..ec529315b4 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -46,36 +46,19 @@ import time from contentstore import course_info_model from models.settings.course_details import CourseDetails from models.settings.course_details import CourseDetailsEncoder +from contentstore.utils import get_modulestore # to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' - - - - - - log = logging.getLogger(__name__) COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video'] -DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info'] - # cdodge: these are categories which should not be parented, they are detached from the hierarchy DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] -def _modulestore(location): - """ - Returns the correct modulestore to use for modifying the specified location - """ - if location.category in DIRECT_ONLY_CATEGORIES: - return modulestore('direct') - else: - return modulestore() - - # ==== Public views ================================================== @ensure_csrf_cookie @@ -543,7 +526,7 @@ def delete_item(request): item = modulestore().get_item(item_location) - store = _modulestore(item_loc) + store = get_modulestore(item_loc) # @TODO: this probably leaves draft items dangling. My preferance would be for the semantic to be @@ -574,7 +557,7 @@ def save_item(request): if not has_access(request.user, item_location): raise PermissionDenied() - store = _modulestore(Location(item_location)); + store = get_modulestore(Location(item_location)); if request.POST.get('data') is not None: data = request.POST['data'] @@ -677,10 +660,10 @@ def clone_item(request): if not has_access(request.user, parent_location): raise PermissionDenied() - parent = _modulestore(template).get_item(parent_location) + parent = get_modulestore(template).get_item(parent_location) dest_location = parent_location._replace(category=template.category, name=uuid4().hex) - new_item = _modulestore(template).clone_item(template, dest_location) + new_item = get_modulestore(template).clone_item(template, dest_location) # TODO: This needs to be deleted when we have proper storage for static content new_item.metadata['data_dir'] = parent.metadata['data_dir'] @@ -689,10 +672,10 @@ def clone_item(request): if display_name is not None: new_item.metadata['display_name'] = display_name - _modulestore(template).update_metadata(new_item.location.url(), new_item.own_metadata) + get_modulestore(template).update_metadata(new_item.location.url(), new_item.own_metadata) if new_item.location.category not in DETACHED_CATEGORIES: - _modulestore(parent.location).update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()]) + get_modulestore(parent.location).update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()]) return HttpResponse(json.dumps({'id': dest_location.url()})) diff --git a/common/djangoapps/models/settings/course_details.py b/common/djangoapps/models/settings/course_details.py index 29820795f3..fb97784588 100644 --- a/common/djangoapps/models/settings/course_details.py +++ b/common/djangoapps/models/settings/course_details.py @@ -5,6 +5,7 @@ import json from json.encoder import JSONEncoder import time from util.converters import jsdate_to_time, time_to_date +from contentstore.utils import get_modulestore class CourseDetails: def __init__(self, location): @@ -28,7 +29,7 @@ class CourseDetails: course = cls(course_location) - descriptor = modulestore('direct').get_item(course_location) + descriptor = get_modulestore(course_location).get_item(course_location) course.start_date = descriptor.start course.end_date = descriptor.end @@ -37,25 +38,25 @@ class CourseDetails: temploc = course_location._replace(category='about', name='syllabus') try: - course.syllabus = modulestore('direct').get_item(temploc).definition['data'] + course.syllabus = get_modulestore(temploc).get_item(temploc).definition['data'] except ItemNotFoundError: pass temploc = temploc._replace(name='overview') try: - course.overview = modulestore('direct').get_item(temploc).definition['data'] + course.overview = get_modulestore(temploc).get_item(temploc).definition['data'] except ItemNotFoundError: pass temploc = temploc._replace(name='effort') try: - course.effort = modulestore('direct').get_item(temploc).definition['data'] + course.effort = get_modulestore(temploc).get_item(temploc).definition['data'] except ItemNotFoundError: pass temploc = temploc._replace(name='video') try: - course.intro_video = modulestore('direct').get_item(temploc).definition['data'] + course.intro_video = get_modulestore(temploc).get_item(temploc).definition['data'] except ItemNotFoundError: pass @@ -69,7 +70,7 @@ class CourseDetails: ## TODO make it an error for this to be undefined & for it to not be retrievable from modulestore course_location = jsondict['course_location'] ## Will probably want to cache the inflight courses because every blur generates an update - descriptor = modulestore('direct').get_item(course_location) + descriptor = get_modulestore(course_location).get_item(course_location) dirty = False @@ -110,21 +111,21 @@ class CourseDetails: descriptor.enrollment_end = converted if dirty: - modulestore('direct').update_metadata(course_location, descriptor.metadata) + get_modulestore(course_location).update_metadata(course_location, descriptor.metadata) # NOTE: below auto writes to the db w/o verifying that any of the fields actually changed # to make faster, could compare against db or could have client send over a list of which fields changed. temploc = Location(course_location)._replace(category='about', name='syllabus') - modulestore('direct').update_item(temploc, jsondict['syllabus']) + get_modulestore(temploc).update_item(temploc, jsondict['syllabus']) temploc = temploc._replace(name='overview') - modulestore('direct').update_item(temploc, jsondict['overview']) + get_modulestore(temploc).update_item(temploc, jsondict['overview']) temploc = temploc._replace(name='effort') - modulestore('direct').update_item(temploc, jsondict['effort']) + get_modulestore(temploc).update_item(temploc, jsondict['effort']) temploc = temploc._replace(name='video') - modulestore('direct').update_item(temploc, jsondict['intro_video']) + get_modulestore(temploc).update_item(temploc, jsondict['intro_video']) # Could just generate and return a course obj w/o doing any db reads, but I put the reads in as a means to confirm From a2f00c383f6f1f373599178b3554bc80000c7080 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 28 Nov 2012 16:12:34 -0500 Subject: [PATCH 061/243] gradable sections - adding in assets and markup - wip --- cms/static/sass/_cms_mixins.scss | 14 ++++++++++++++ cms/static/sass/_courseware.scss | 15 +++++++++++++++ cms/static/sass/_variables.scss | 1 + cms/templates/overview.html | 4 ++++ 4 files changed, 34 insertions(+) diff --git a/cms/static/sass/_cms_mixins.scss b/cms/static/sass/_cms_mixins.scss index 2f43bfd208..1035729717 100644 --- a/cms/static/sass/_cms_mixins.scss +++ b/cms/static/sass/_cms_mixins.scss @@ -47,6 +47,20 @@ } } +@mixin green-button { + @include button; + border: 1px solid #0d7011; + border-radius: 3px; + @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); + background-color: $green; + color: #fff; + + &:hover { + background-color: #129416; + color: #fff; + } +} + @mixin white-button { @include button; border: 1px solid $darkGrey; diff --git a/cms/static/sass/_courseware.scss b/cms/static/sass/_courseware.scss index 094b6183dd..536b54c31d 100644 --- a/cms/static/sass/_courseware.scss +++ b/cms/static/sass/_courseware.scss @@ -73,6 +73,21 @@ input.courseware-unit-search-input { } } + .section-gradable-status { + position: absolute; + top: 25px; + right: 330px; + + input[type=checkbox] { + position: absolute; + top: -9999px; + left: -9999px; + } + label { + cursor: pointer; + } + } + .datepair .date, .datepair .time { padding-left: 0; diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index fec65e4e11..49268c0fc0 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -15,6 +15,7 @@ $error-red: rgb(253, 87, 87); $blue: #5597dd; $orange: #edbd3c; +$green: #108614; $lightGrey: #edf1f5; $mediumGrey: #ced2db; $darkGrey: #8891a1; diff --git a/cms/templates/overview.html b/cms/templates/overview.html index cc0d7e8e32..7c3820d448 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -102,6 +102,10 @@ Edit %endif
                              +
                              + + +
                              From c0da6e42492e49b704938acda8822dbbedcdcce5 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 28 Nov 2012 17:10:41 -0500 Subject: [PATCH 062/243] Details done except for syllabus --- cms/djangoapps/contentstore/views.py | 6 +- .../js/models/settings/course_details.js | 67 ++ .../js/views/settings/main_settings_view.js | 21 +- cms/static/sass/_settings.scss | 820 +----------------- cms/templates/settings.html | 4 +- .../models/settings/course_details.py | 2 + 6 files changed, 87 insertions(+), 833 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index ec529315b4..8bd07420d7 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -647,8 +647,6 @@ def unpublish_unit(request): return HttpResponse() - - @login_required @expect_json def clone_item(request): @@ -952,11 +950,13 @@ def get_course_settings(request, org, course, name): raise PermissionDenied() course_module = modulestore().get_item(location) + course_details = CourseDetails.fetch(location) return render_to_response('settings.html', { 'active_tab': 'settings-tab', 'context_course': course_module, - 'course_details' : json.dumps(CourseDetails.fetch(location), cls=CourseDetailsEncoder) + 'course_details' : json.dumps(course_details, cls=CourseDetailsEncoder), + 'video_editor_html' : preview_component(request, course_details.intro_video_loc) }) @expect_json diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 3ae4a86f90..46a31bb9fe 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -36,5 +36,72 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ urlRoot: function() { var location = this.get('location'); return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; + }, + + _videoprefix : /\s* or just the "speed:key, *" string + // returns the videosource for the preview which iss the key whose speed is closest to 1 + if (newsource == null) this.save({'intro_video': null}); + else if (this._getNextMatch(this._videoprefix, newsource, 0)) this.save('intro_video', newsource); + else this.save('intro_video', '
                              - - Upload Video - + Video restrictions go here
                              diff --git a/common/djangoapps/models/settings/course_details.py b/common/djangoapps/models/settings/course_details.py index fb97784588..7d708e21c4 100644 --- a/common/djangoapps/models/settings/course_details.py +++ b/common/djangoapps/models/settings/course_details.py @@ -17,6 +17,7 @@ class CourseDetails: self.syllabus = None # a pdf file asset self.overview = "" # html to render as the overview self.intro_video = None # a video pointer + self.intro_video_loc = None # a video pointer self.effort = None # int hours/week @classmethod @@ -57,6 +58,7 @@ class CourseDetails: temploc = temploc._replace(name='video') try: course.intro_video = get_modulestore(temploc).get_item(temploc).definition['data'] + course.intro_video_loc = temploc except ItemNotFoundError: pass From 7ed0002bd237564ce6fd4c9daf6b5908c7120f2f Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Thu, 29 Nov 2012 16:18:36 -0500 Subject: [PATCH 063/243] Details now has validation except for youtube ids which cause crossdomain policy violations when i try to validate. --- .../js/models/settings/course_details.js | 65 ++++++++++++++++- .../js/views/settings/main_settings_view.js | 69 +++++++++++++++---- 2 files changed, 118 insertions(+), 16 deletions(-) diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index 46a31bb9fe..91e211be95 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -33,20 +33,57 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ return attributes; }, + validate: function(newattrs) { + // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs + // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation. + var errors = {}; + if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { + errors.end_date = "The course end date cannot be before the course start date."; + } + if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) { + errors.enrollment_start = "The course start date cannot be before the enrollment start date."; + } + if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) { + errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date."; + } + if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) { + errors.enrollment_end = "The enrollment end date cannot be after the course end date."; + } + if (newattrs.intro_video && newattrs.intro_video != this.get('intro_video')) { + var videos = this.parse_videosource(newattrs.intro_video); + var vid_errors = new Array(); + var cachethis = this; + for (var i=0; i or just the "speed:key, *" string // returns the videosource for the preview which iss the key whose speed is closest to 1 if (newsource == null) this.save({'intro_video': null}); + // TODO remove all whitespace w/in string else if (this._getNextMatch(this._videoprefix, newsource, 0)) this.save('intro_video', newsource); else this.save('intro_video', '
                              From 46d4e27cdc9f5233d86ae1557ab1a5163498671d Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Mon, 26 Nov 2012 14:40:42 -0500 Subject: [PATCH 068/243] update styles fixed; tweaks to white button mixins; add update to top of list; render updates upon save --- .../client_templates/course_info_update.html | 18 ++++---- cms/static/img/delete-icon.png | Bin 970 -> 2837 bytes cms/static/img/edit-icon.png | Bin 1066 -> 2931 bytes cms/static/js/template_loader.js | 2 +- cms/static/js/views/course_info_edit.js | 17 +++---- cms/static/sass/_cms_mixins.scss | 22 ++++----- cms/static/sass/_course-info.scss | 42 ++++++++++++------ cms/templates/course_info.html | 3 +- 8 files changed, 62 insertions(+), 42 deletions(-) diff --git a/cms/static/coffee/src/client_templates/course_info_update.html b/cms/static/coffee/src/client_templates/course_info_update.html index 54a4a38dde..bc686109c2 100644 --- a/cms/static/coffee/src/client_templates/course_info_update.html +++ b/cms/static/coffee/src/client_templates/course_info_update.html @@ -16,13 +16,15 @@ Cancel
                              -

                              - <%= - updateModel.get('date') %> -

                              -
                              <%= updateModel.get('content') %>
                              -
                              - Edit - Delete +
                              +
                              + Edit + Delete +
                              +

                              + <%= + updateModel.get('date') %> +

                              +
                              <%= updateModel.get('content') %>
                              \ No newline at end of file diff --git a/cms/static/img/delete-icon.png b/cms/static/img/delete-icon.png index 1855a2943dc4691222216a3a2a5b79ebc51a6c00..9c7f65daef99b990cadfca53267aadb46323cc04 100644 GIT binary patch delta 2830 zcmV+p3-R>I2bC6(B!3BTNLh0L01FcU01FcV0GgZ_000V4X+uL$P-t&-Z*ypGa3D!T zLm+T+Z)Rz1WdHzp+MQEpR8#2|J@?-9LQ9B%luK_?6$l_wLW_VDktQl32@pz%A)(n7 zQNa;KMFbnjpojyGj)066Q7jCK3fKqaA)=0hqlk*i`{8?|Yk$_f_vX$1wbwr9tn;0- z&j-K=43f59&ghTmgWD0l;*TI7}*0BAb^tj|`8MF3bZ02F3R#5n-i zEdVe{S7t~6u(trf&JYW-00;~KFj0twDF6g}0AR=?BX|IWnE(_<@>e|ZE3OddDgXd@ znX){&BsoQaTL>+22Uk}v9w^R97b_GtVFF>AKrX_0nSU8Ffiw@`^UMGMppg|3;Dhu1 zc+L*4&dxTDwhmt{>c0m6B4T3W{^ifBa6kY6;dFk{{wy!E8h|?nfNlPwCGG@hUJIag z_lst-4?wj5py}FI^KkfnJUm6Akh$5}<>chpO2k52Vaiv1{%68pz*qfj`F=e7_x0eu z;v|7GU4MZ`1o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcqjPo+3 zB8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S1Au6Q z;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO0Dk~Ppn)o|K^yeJ7%adB9Ki+L!3+Fg zHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_vKpix|QD}yfa1JiQRk#j4a1Z)n2%fLC6RbVIkUx0b+_+BaR3cnT7Zv!AJxWizFb)h!jyGOOZ85F;a?DAXP{m@;!0_ zIe&*-M!JzZ$N(~e{D!NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWw%BIv?Wdily+ylO`+*KY$4Vz$Cr4+G&IO(4Q`uA9rwXSQO+7mGt}d!;r5mBU zM0dY#r|y`ZzFvTyOmC;&dA;ZQ9DOhSRQ+xGr}ak+SO&8UBnI0I&KNw!HF0k|9WTe* z@liuv!$3o&VU=N*;e?U7(SJOn)kcj*4~%KXT;n9;ZN_cJqb3F>Atp;r>P_yNQcbz0 zDW*G2J50yT%*~?B)|oY%Ju%lZ=bPu7*PGwBU|M)uEVih&xMfMQu79>|wtZn|Vi#w( z#jeBdlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!h;8Eq#KMS9gFl*neeosSBfoHYnBQIkwkyowPu(zdm zs`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMeBmZRodjHV?r+_5^X9J0W zL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0?0=B0A@}E)&XLY(4uw#D z=+@8&Vdi0r!+s1Wg@=V#hChyQh*%oYF_$%W(cD9G-$eREmPFp0XE9GXuPsV7Dn6<% zYCPIEx-_~!#x7=A%+*+(SV?S4962s3t~PFLzTf=q^M~S{;tS(@7nm=|U2u7!&cgJC zrxvL$5-d8FKz~e#PB@hCK@cja7K|nG6L%$!3VFgE!e=5c(KgYD*h5?@9!~N|DouKl z?2)`Rc_hU%r7Y#SgeR$xyi5&D-J3d|7MgY-Z8AMNy)lE5k&tmhsv%92wrA>R=4N)w ztYw9={>5&Kw=W)*2gz%*kgNq+Eef_mrsz~!DAy_nvVUh~S7yJ>iOM;atDY;(?aZ^v z+mJV$@1Ote62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~p zu715HdQEGAUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$ z+<4_1hktL%znR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX4c}I@?e+FW+b@^R zDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&_B8C(+grT%{XWUQ z+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?SIDu(gXbmBM!FLxzyDi(mhmCkJc;e zM-ImyzW$x>cP$Mz4ONYt#^NJzM0w=t_X*$k9t}F$c8q(h;Rn+nb{%IOFKR-X@|s4Q zQ=0o*Vq3aT%s$c9>fU<%N829{oHRUHc}nwC$!Xf@g42^{^3RN&m7RTlF8SPG+oHC6 z=YM0)-)awU@466l;nGF_i|0GMJI-A4xODQe+vO8ixL2C5I$v$-bm~0*lhaSfyPUh4 zuDM)mx$b(swR>jw=^LIm&fWCAdGQwi*43UlJ>9+YdT;l|_x0Zv-F|W>{m#p~*>@-I zt-MdXU-UrjLD@syht)q@{@mE_+<$7ocYmPs(cDM(28Dyq{*m>M4?_iynUBkc4TkHU zI6gT!;y-fz>HMcd&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M z!p0uH$#^p{Ui4P`?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&Gk-1H z0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}0000$Nkl`rg gt72eaU;qpN!!!r_LCqc|00000Ne4wvM6N<$f_}C{Z2$lO literal 970 zcmaJ=-HOvd6pj{ZSzS={LS@k*+Y1$&pZ+9mXu7sd>jq3IZNXl6v1u}GLpPb2Ol{g5 zK@jm3^aTV##2YWXP-H=T0}*`&-3Jhz>9$=jtYOHUIh^l1=gj%$X|r*AbK~*`!!Vor zuGS(uwNEGF2$idYur&R zpB;Ud>3Xoe~7M80W_AO<}+a6Fa$@$Nkf99w1Y<_y8`t8nP-&H~t;H9FR8WEE_7 zrv!?TLI_-lO%S>JUZ_MWJLgr1P2D^T<{@~bvPVUAjV7p~0D>HsNm+s@0!iY;Y)+EI zn;3 z82=A--BommTkyEve-ekCsSo)U4AD5S$l&_hG?lMZ188CtbP(EK-ePlzFbapr2i3L& zt{JA~c+_!yjxY>G_d;xX7SuJBB^;dN*h;OE&u3*#s+X&>DAvS^oX+IsLPe0OQl(ta zF0mT2#xC^m5^Eo0<+WHU2G=Kf59&ghTmgWD0l;*TI7}*0BAb^tj|`8MF3bZ02F3R#5n-i zEdVe{S7t~6u(trf&JYW-00;~KFj0twDF6g}0AR=?BX|IWnE(_<@>e|ZE3OddDgXd@ znX){&BsoQaTL>+22Uk}v9w^R97b_GtVFF>AKrX_0nSU8Ffiw@`^UMGMppg|3;Dhu1 zc+L*4&dxTDwhmt{>c0m6B4T3W{^ifBa6kY6;dFk{{wy!E8h|?nfNlPwCGG@hUJIag z_lst-4?wj5py}FI^KkfnJUm6Akh$5}<>chpO2k52Vaiv1{%68pz*qfj`F=e7_x0eu z;v|7GU4MZ`1o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcqjPo+3 zB8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S1Au6Q z;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO0Dk~Ppn)o|K^yeJ7%adB9Ki+L!3+Fg zHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_vKpix|QD}yfa1JiQRk#j4a1Z)n2%fLC6RbVIkUx0b+_+BaR3cnT7Zv!AJxWizFb)h!jyGOOZ85F;a?DAXP{m@;!0_ zIe&*-M!JzZ$N(~e{D!NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWw%BIv?Wdily+ylO`+*KY$4Vz$Cr4+G&IO(4Q`uA9rwXSQO+7mGt}d!;r5mBU zM0dY#r|y`ZzFvTyOmC;&dA;ZQ9DOhSRQ+xGr}ak+SO&8UBnI0I&KNw!HF0k|9WTe* z@liuv!$3o&VU=N*;e?U7(SJOn)kcj*4~%KXT;n9;ZN_cJqb3F>Atp;r>P_yNQcbz0 zDW*G2J50yT%*~?B)|oY%Ju%lZ=bPu7*PGwBU|M)uEVih&xMfMQu79>|wtZn|Vi#w( z#jeBdlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!h;8Eq#KMS9gFl*neeosSBfoHYnBQIkwkyowPu(zdm zs`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMeBmZRodjHV?r+_5^X9J0W zL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0?0=B0A@}E)&XLY(4uw#D z=+@8&Vdi0r!+s1Wg@=V#hChyQh*%oYF_$%W(cD9G-$eREmPFp0XE9GXuPsV7Dn6<% zYCPIEx-_~!#x7=A%+*+(SV?S4962s3t~PFLzTf=q^M~S{;tS(@7nm=|U2u7!&cgJC zrxvL$5-d8FKz~e#PB@hCK@cja7K|nG6L%$!3VFgE!e=5c(KgYD*h5?@9!~N|DouKl z?2)`Rc_hU%r7Y#SgeR$xyi5&D-J3d|7MgY-Z8AMNy)lE5k&tmhsv%92wrA>R=4N)w ztYw9={>5&Kw=W)*2gz%*kgNq+Eef_mrsz~!DAy_nvVUh~S7yJ>iOM;atDY;(?aZ^v z+mJV$@1Ote62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~p zu715HdQEGAUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$ z+<4_1hktL%znR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX4c}I@?e+FW+b@^R zDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&_B8C(+grT%{XWUQ z+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?SIDu(gXbmBM!FLxzyDi(mhmCkJc;e zM-ImyzW$x>cP$Mz4ONYt#^NJzM0w=t_X*$k9t}F$c8q(h;Rn+nb{%IOFKR-X@|s4Q zQ=0o*Vq3aT%s$c9>fU<%N829{oHRUHc}nwC$!Xf@g42^{^3RN&m7RTlF8SPG+oHC6 z=YM0)-)awU@466l;nGF_i|0GMJI-A4xODQe+vO8ixL2C5I$v$-bm~0*lhaSfyPUh4 zuDM)mx$b(swR>jw=^LIm&fWCAdGQwi*43UlJ>9+YdT;l|_x0Zv-F|W>{m#p~*>@-I zt-MdXU-UrjLD@syht)q@{@mE_+<$7ocYmPs(cDM(28Dyq{*m>M4?_iynUBkc4TkHU zI6gT!;y-fz>HMcd&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M z!p0uH$#^p{Ui4P`?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&Gk-1H z0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}00019R>mo5(Wic3Rd;%85YV^@e>>`6nELT2+Y`%bqGFk5Gy3X-#Z~dS8 zKkr+bFIRp{6|ZCoe!wG~(fcP*$7KRd?9nd@3{b-qH#D$DIryTn!vPmeaK#MoFbCem zMY}j+h)z;G_yQ->LObtDdGI6FIi(fC0(gulTQI~Hm4DzwDGlF?;B4t<>>L%n{xbkV W?jOVe&dqQD0000xb=I7wG2(ZNu*YG5`&^!D3^D9u0wGH_baBIM4CO%f<_guKx$_yr>dbINeZ zgyW^rv{agr!ZO+42YPJ|3#br@z*h5`#n}<^P?y7d$4ryp5Q1hR5dXZcfuoI2?8~0s$XJ_^de%iMCI(yk!L*T9T<4NYORmD2f@qfFdN0^e6?@sK{#8 zahY(zXj?RBhVnZpm4Slrf2gWf&=yL;lX(9rY^CQ6NT;Bs7fcB^F6(t%8C=YSBGS#Y zuII~D9LwoQw{p4xV&g2hB#4rtIfl!J3_;+MnuSD7f=NC?Vhu`BWG)&=gc6AmpBRkd zy2qJlPoOK*6OQ`X7#kf-1j}4rmkKJ>P?;;AaN||E&M>G3_RK?5nT2w~)KzepG^bRL zC0bSQm@8M0C03P7<6vl~wSTp`Y{K*7Y%7C{50%k}8lHC(5BBP>brWBTwIn~7wm)Si zpYMBWT(9chd=Wb?x>r6g?6IEqvyT@~Ew{Qif}R~$#yj8EvGAaM`Oe~=*VEwIErG1` z`s=DX@Um%n$Gy?&>34n0frIU(_-^f!p=S$rzvrrZqwU@IkxiAgKX}8>V?>3{RrC{Ejna#9pt=)aP*VVWE^U* <%block name="title">Course Info +<%block name="bodyclass">course-info <%block name="jsextra"> @@ -33,7 +34,7 @@

                              Course Info

                              -
                              +

                              Updates

                              New Update
                                From 3d9961f8744a5cd570a314056dba2d5ff326b9d8 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Mon, 26 Nov 2012 14:55:10 -0500 Subject: [PATCH 069/243] started adding date picker --- .../coffee/src/client_templates/course_info_update.html | 7 ++++--- cms/static/js/views/course_info_edit.js | 7 +++---- cms/templates/course_info.html | 4 ++++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cms/static/coffee/src/client_templates/course_info_update.html b/cms/static/coffee/src/client_templates/course_info_update.html index bc686109c2..00f8ff02c8 100644 --- a/cms/static/coffee/src/client_templates/course_info_update.html +++ b/cms/static/coffee/src/client_templates/course_info_update.html @@ -4,8 +4,9 @@
                                - +
                                + +
                                @@ -16,7 +17,7 @@ Cancel
                                -
                                +
                                Edit Delete diff --git a/cms/static/js/views/course_info_edit.js b/cms/static/js/views/course_info_edit.js index ff266ad4f5..1efd4db026 100644 --- a/cms/static/js/views/course_info_edit.js +++ b/cms/static/js/views/course_info_edit.js @@ -36,10 +36,9 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ // TODO Where should the template reside? how to use the static.url to create the path? "/static/coffee/src/client_templates/course_info_update.html", function (raw_template) { - console.log(raw_template); self.template = _.template(raw_template); - self.render(); - } + self.render(); + } ); }, @@ -65,7 +64,7 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({ var newForm = this.template({ updateModel : newModel }); var updateEle = this.$el.find("#course-update-list"); $(updateEle).prepend(newForm); - $(newForm).find(".new-update-form").slideDown(150); + $(newForm).find(".new-update-form").show(); }, onSave: function(event) { diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html index ae0dac197e..30a12244e2 100644 --- a/cms/templates/course_info.html +++ b/cms/templates/course_info.html @@ -9,6 +9,10 @@ + + + + - - + + + + + + + + + + + + + <%block name="header_extras"> @@ -102,11 +146,25 @@ Edit %endif
                                -
                                - - + +
                                +
                                @@ -122,13 +180,31 @@ % for subsection in section.get_children():
                              1. Course Details
                              2. ', status_code=200, html=True) + + # resp s/b json from here on + url = reverse('contentstore.views.course_settings_updates', kwargs=details_loc) + resp = self.client.get(url) + jsondetails = json.dumps(details, cls=CourseDetailsEncoder) + self.assertDictEqual(resp, jsondetails, "virgin get") + + self.alter_field(url, details, 'start_date', time.time() * 1000) + self.alter_field(url, details, 'start_date', time.time() * 1000 + 60 * 60 * 24) + self.alter_field(url, details, 'end_date', time.time() * 1000 + 60 * 60 * 24 * 100) + self.alter_field(url, details, 'enrollment_start', time.time() * 1000) + + self.alter_field(url, details, 'enrollment_end', time.time() * 1000 + 60 * 60 * 24 * 8) + self.alter_field(url, details, 'syllabus', "bar") + self.alter_field(url, details, 'overview', "Overview") + self.alter_field(url, details, 'intro_video', "intro_video") + self.alter_field(url, details, 'effort', "effort") diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index e239da2a19..5b91b77554 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -955,8 +955,7 @@ def get_course_settings(request, org, course, name): return render_to_response('settings.html', { 'active_tab': 'settings-tab', 'context_course': course_module, - 'course_details' : json.dumps(course_details, cls=CourseDetailsEncoder), - 'video_editor_html' : preview_component(request, course_details.intro_video_loc) + 'course_details' : json.dumps(course_details, cls=CourseDetailsEncoder) }) @expect_json diff --git a/cms/djangoapps/models/__init__.py b/cms/djangoapps/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cms/djangoapps/models/settings/course_details.py b/cms/djangoapps/models/settings/course_details.py index ea800a3173..7f89589c60 100644 --- a/cms/djangoapps/models/settings/course_details.py +++ b/cms/djangoapps/models/settings/course_details.py @@ -17,7 +17,6 @@ class CourseDetails: self.syllabus = None # a pdf file asset self.overview = "" # html to render as the overview self.intro_video = None # a video pointer - self.intro_video_loc = None # a video pointer self.effort = None # int hours/week @classmethod @@ -58,7 +57,6 @@ class CourseDetails: temploc = temploc._replace(name='video') try: course.intro_video = get_modulestore(temploc).get_item(temploc).definition['data'] - course.intro_video_loc = temploc except ItemNotFoundError: pass diff --git a/cms/djangoapps/models/settings/course_faculty.py b/cms/djangoapps/models/settings/course_faculty.py new file mode 100644 index 0000000000..c1812614ec --- /dev/null +++ b/cms/djangoapps/models/settings/course_faculty.py @@ -0,0 +1,22 @@ +from xmodule.modulestore import Location +class CourseFaculty: + def __init__(self, location): + if not isinstance(location, Location): + location = Location(location) + # course_location is used so that updates know where to get the relevant data + self.course_location = location + self.first_name = "" + self.last_name = "" + self.photo = None + self.bio = "" + + + @classmethod + def fetch(cls, course_location): + """ + Fetch a list of faculty for the course + """ + if not isinstance(course_location, Location): + course_location = Location(course_location) + + # Must always have at least one faculty member (possibly empty) \ No newline at end of file diff --git a/common/djangoapps/util/converters.py b/common/djangoapps/util/converters.py index 6070338b55..4ff5b287f3 100644 --- a/common/djangoapps/util/converters.py +++ b/common/djangoapps/util/converters.py @@ -10,12 +10,12 @@ def time_to_date(time_obj): def jsdate_to_time(field): """ - Convert a true universal time (msec since epoch) from a string to a time obj + Convert a universal time (iso format) or msec since epoch to a time obj """ if field is None: return field - elif isinstance(field, unicode): # iso format but ignores time zone assuming it's Z - d=datetime.datetime(*map(int, re.split('[^\d]', field)[:-1])) + elif isinstance(field, unicode) or isinstance(field, str): # iso format but ignores time zone assuming it's Z + d=datetime.datetime(*map(int, re.split('[^\d]', field)[:6])) # stop after seconds. Debatable return d.utctimetuple() - elif isinstance(field, int): + elif isinstance(field, int) or isinstance(field, float): return time.gmtime(field / 1000) \ No newline at end of file From fc26edb1563bb3a5d85c259d3a6ac23be8c3c477 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 3 Dec 2012 16:13:18 -0500 Subject: [PATCH 082/243] grading - added in sub section controls --- cms/static/sass/_courseware.scss | 15 ++++++++++----- cms/static/sass/_subsection.scss | 20 +++++++++----------- cms/templates/edit_subsection.html | 2 +- cms/templates/overview.html | 2 +- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/cms/static/sass/_courseware.scss b/cms/static/sass/_courseware.scss index 65b6d648ed..8613a56d37 100644 --- a/cms/static/sass/_courseware.scss +++ b/cms/static/sass/_courseware.scss @@ -158,11 +158,12 @@ input.courseware-unit-search-input { top: 0; right: 2px; display: none; - width: 105px; - padding: 10px 20px 10px 10px; + width: 100px; + padding: 10px 35px 10px 10px; @include border-radius(3px); background: $lightGrey; color: $lightGrey; + text-align: right; font-size: 12px; font-weight: bold; line-height: 16px; @@ -183,7 +184,7 @@ input.courseware-unit-search-input { .menu { z-index: 1; - display: block; + display: none; opacity: 0.0; position: absolute; top: -1px; @@ -196,6 +197,7 @@ input.courseware-unit-search-input { @include border-radius(4px); @include box-shadow(0 1px 2px rgba(0, 0, 0, .2)); @include transition(opacity .15s); + @include transition(display .15s); li { @@ -228,6 +230,7 @@ input.courseware-unit-search-input { .menu { z-index: 1000; + display: block; opacity: 1.0; } @@ -317,9 +320,10 @@ input.courseware-unit-search-input { right: -5px; display: none; width: 110px; - padding: 5px 20px 5px 10px; + padding: 5px 40px 5px 10px; @include border-radius(3px); color: $lightGrey; + text-align: right; font-size: 12px; font-weight: bold; line-height: 16px; @@ -340,7 +344,7 @@ input.courseware-unit-search-input { .menu { z-index: 1; - display: block; + display: none; opacity: 0.0; position: absolute; top: -1px; @@ -386,6 +390,7 @@ input.courseware-unit-search-input { .menu { z-index: 1000; + display: block; opacity: 1.0; } diff --git a/cms/static/sass/_subsection.scss b/cms/static/sass/_subsection.scss index 25273c1ad9..116c887e10 100644 --- a/cms/static/sass/_subsection.scss +++ b/cms/static/sass/_subsection.scss @@ -137,8 +137,7 @@ a { font-size: 11px; - font-weight: 700; - line-height: 31px; + font-weight: bold; text-transform: uppercase; } @@ -199,11 +198,11 @@ margin: 0; padding: 0; background: transparent; - color: $darkGrey; + color: $blue; border: none; - font-size: 12px; + font-size: 11px; font-weight: bold; - line-height: 16px; + text-transform: uppercase; } .menu-toggle { @@ -221,10 +220,11 @@ } .menu { - z-index: 10; + z-index: 1; position: absolute; top: -12px; left: -7px; + display: none; width: 100%; margin: 0; padding: 8px 12px; @@ -246,10 +246,6 @@ margin-bottom: 0; padding-bottom: 0; border: none; - - a { - color: $darkGrey; - } } } @@ -265,11 +261,13 @@ &.is-active { .menu { + z-index: 10000; + display: block; opacity: 1.0; } .menu-toggle { - z-index: 10000; + z-index: 1000; } } diff --git a/cms/templates/edit_subsection.html b/cms/templates/edit_subsection.html index 243df7e57c..6eed139f00 100644 --- a/cms/templates/edit_subsection.html +++ b/cms/templates/edit_subsection.html @@ -86,7 +86,7 @@
                                - +

                                Not Graded

                                diff --git a/cms/templates/overview.html b/cms/templates/overview.html index 200fecfc97..b31bd203b5 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -152,7 +152,7 @@

                                Not Graded

                                - +