From 8da6543d901e8339383d4a06bda18d85ff2003f3 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 2 Jul 2013 18:31:37 -0400 Subject: [PATCH 01/71] Studio: adds static states for all authorship cases on dashboard (WIP) --- cms/templates/index.html | 379 +++++++++++++++++++++++++++++++++++---- 1 file changed, 349 insertions(+), 30 deletions(-) diff --git a/cms/templates/index.html b/cms/templates/index.html index e7205c9430..5c5b631ef3 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -57,37 +57,356 @@
+ + +
-
-

- ${_("Welcome, %(name)s") % dict(name= user.username)}. - ${_("Here are all of the courses you are currently authoring in Studio:")}

-
+
+ +
+

${_("Thanks for signing up, %(name)s!") % dict(name= user.username)}

+
+ +
+

${_('Next Steps to Authoring in Studio')}

+
+

${_('Your on your way to authoring courses online using Studio. In order to complete your registration, we need you verify your registration by checking your $emailaddress email account. An activation message and next steps should be waiting for you there.')}

+ +

${_('Need another copy of the verification email? You can also request another message be sent.')}

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

${_("Welcome, %(name)s!") % dict(name= user.username)}

+
+ +
+

${_('Your Account Has Been Verified')}

+
+

${_('Thanks for verifying your edX Studio account.Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam.')}

+
+
+ +
+

${_('Creating Your Own Courses in Studio')}

+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+ +
+

${_('Your Authorship Request Status')}

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

${_("Welcome, %(name)s !") % dict(name= user.username)}

+
+ +
+

${_('Creating Your Own Courses in Studio')}

+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+ +
+

${_('Your Authorship Request Status')}

+
+ +
+ +
+
${_('Your authorship request is:')}
+
+ ${_('Pending')} + ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} +
+
+
+
+
+ + +
+ + + + + +
+
+
+

${_("Welcome, %(name)s !") % dict(name= user.username)}

+
+ +
+

${_('Creating Your Own Courses in Studio')}

+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+ +
+

${_('Your Authorship Request Status')}

+
+ +
+ +
+
${_('Your authorship request is:')}
+
+ ${_('Rejected')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+ +
+
${_('The following feedback was given by edX staff when making this decision')}
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

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

${_("Welcome, %(name)s") % dict(name= user.username)}!

+
+

${_("Here are all of the courses you currently have access to in Studio:")}

+
+
+ + + +
+

${_('Creating Your Own Courses in Studio')}

+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+ +
+

${_('Your Authorship Request Status')}

+
+ +
+ +
+
${_('Your authorship request is:')}
+
+ ${_('Rejected')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+ +
+
${_('The following feedback was given by edX staff when making this decision')}
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+
+
+
+
+ + +
+ + + + + +
+ + +
- -
-
-
- % if user.is_active: -
    - %for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower() if s[0] is not None else ''): -
  • - - ${course} - - View Live -
  • - %endfor -
- % else: -
-

- ${_("In order to start authoring courses using edX Studio, please click on the activation link in your email.")} -

-
- % endif -
-
-
+ +#
+# % if user.is_active: +# + +# % else: +#
+#

${_("In order to start authoring courses using edX Studio, please click on the activation link in your email.")}

+#
+# % endif +#
From 8f5c972e79cccec81d0b641646f7dc70cc835085 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 3 Jul 2013 10:36:19 -0400 Subject: [PATCH 02/71] Studio: revises static states for all authorship cases on dashboard (WIP) --- cms/templates/index.html | 342 ++++++++++++++++++++------------------- 1 file changed, 175 insertions(+), 167 deletions(-) diff --git a/cms/templates/index.html b/cms/templates/index.html index 5c5b631ef3..5a1003b391 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -67,7 +67,7 @@

${_("Thanks for signing up, %(name)s!") % dict(name= user.username)}

-
+

${_('Next Steps to Authoring in Studio')}

${_('Your on your way to authoring courses online using Studio. In order to complete your registration, we need you verify your registration by checking your $emailaddress email account. An activation message and next steps should be waiting for you there.')}

@@ -97,10 +97,6 @@
-
-

${_("Welcome, %(name)s!") % dict(name= user.username)}

-
-

${_('Your Account Has Been Verified')}

@@ -108,24 +104,54 @@
-
-

${_('Creating Your Own Courses in Studio')}

+
+

${_("Welcome, %(name)s!") % dict(name= user.username)}

+
-

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+

${_("You don't have any courses you're working on in Studio yet.")}

+
-
-

${_('Your Authorship Request Status')}

+
+ +
+

${_('Create Your First Course')}

- +

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+ + +
+

+ ${_('Becoming a Course Author in Studio')} +

+ +
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+ +
+

${_('Request the Ability to Author Courses in Studio')}

+
+ +
+ + +
+
+
@@ -150,27 +176,36 @@

${_("Welcome, %(name)s !") % dict(name= user.username)}

+ +
+

${_("You don't have any courses you're working on in Studio yet.")}

+
-
-

${_('Creating Your Own Courses in Studio')}

-
-

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

-
+
+ +
+

+ ${_('Becoming a Course Author in Studio')} +

-
-

${_('Your Authorship Request Status')}

-
+
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+
+

${_('Your Authorship Request Status')}

+ +
+
${_('Your authorship request is:')}
+
+ ${_('Pending')} + ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} +
+
+
- -
-
${_('Your authorship request is:')}
-
- ${_('Pending')} - ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} -
-
@@ -188,34 +223,42 @@

${_("Welcome, %(name)s !") % dict(name= user.username)}

-
-
-

${_('Creating Your Own Courses in Studio')}

-

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+

${_("You don't have any courses you're working on in Studio yet.")}

+
-
-

${_('Your Authorship Request Status')}

-
+
+ + - -
-
${_('Your authorship request is:')}
-
- ${_('Rejected')} - ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} -
-
- -
-
${_('The following feedback was given by edX staff when making this decision')}
-
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

-
-
+
+ +
+

${_('Your Authorship Request Status')}

+ +
+
${_('Your authorship request is:')}
+
+ ${_('Rejected')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+ +
+
${_('The following feedback was given by edX staff when making this decision')}
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+
+
@@ -226,91 +269,93 @@
- - - -
-
- -
- - -
- - - - +
-

${_("Welcome, %(name)s") % dict(name= user.username)}!

+

${_("Welcome, %(name)s !") % dict(name= user.username)}

+

${_("Here are all of the courses you currently have access to in Studio:")}

-
-
-
-

${_('Your Authorship Request Status')}

-
+ + - -
-
${_('Your authorship request is:')}
-
- ${_('Rejected')} - ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} -
-
- -
-
${_('The following feedback was given by edX staff when making this decision')}
-
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

-
-
+
+ +
+

${_('Your Authorship Request Status')}

+ +
+
${_('Your authorship request is:')}
+
+ ${_('Rejected')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+ +
+
${_('The following feedback was given by edX staff when making this decision')}
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+
+
+
+ + +
+

+ ${_('Becoming a Course Author in Studio')} +

+ +
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+ +
+

${_('Your Authorship Request Status')}

+ +
+
${_('Your authorship request is:')}
+
+ ${_('Rejected')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+ +
+
${_('The following feedback was given by edX staff when making this decision')}
+
+

${_('Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Nullam quis risus eget urna mollis ornare vel eu leo. Donec id elit non mi porta gravida at eget metus.')}

+
+
+
@@ -329,8 +374,7 @@
- - +
@@ -390,23 +415,6 @@
-#
-# % if user.is_active: -#
    -# %for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower() if s[0] is not None else ''): -#
  • -# -# ${course} -# - -# View Live -#
  • -# %endfor -#
- -# % else: -#
-#

${_("In order to start authoring courses using edX Studio, please click on the activation link in your email.")}

-#
-# % endif -#
+# % if user.is_active: +# % else: +# % endif From 4d9d6a4522acb58aa35b93b6ea9dcbd96498ee72 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Sun, 7 Jul 2013 18:49:11 -0400 Subject: [PATCH 03/71] Studio: resolves blue primary button default + hover color states --- cms/static/sass/elements/_controls.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cms/static/sass/elements/_controls.scss b/cms/static/sass/elements/_controls.scss index 6859560361..4fac3de01f 100644 --- a/cms/static/sass/elements/_controls.scss +++ b/cms/static/sass/elements/_controls.scss @@ -26,13 +26,13 @@ // blue primary button .btn-primary-blue { @extend .btn-primary; - background: $blue; - border-color: $blue-s1; + background: $blue-u1; + border-color: $blue-u1; color: $white; &:hover, &:active { - background: $blue-s2; - border-color: $blue-s2; + background: $blue-s1; + border-color: $blue-s1; } &.current, &.active { From 394292ec1bda2bc680e4f2c59e111c7719f45f23 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Sun, 7 Jul 2013 18:50:36 -0400 Subject: [PATCH 04/71] Studio: revises static dashboard states for all authorship rights scenarios --- cms/static/sass/_base.scss | 36 -- cms/static/sass/elements/_system-help.scss | 153 ++++- cms/static/sass/views/_dashboard.scss | 248 ++++++++- cms/templates/index.html | 613 +++++++++++++++------ 4 files changed, 821 insertions(+), 229 deletions(-) diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 4a20a98eb3..fe107d7511 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -368,42 +368,6 @@ p, ul, ol, dl { color: $gray-d3; } } - - .introduction { - @include box-sizing(border-box); - @extend .t-copy-sub1; - width: flex-grid(12); - margin: 0 0 $baseline 0; - - .copy strong { - font-weight: 600; - } - - &.has-links { - @include clearfix(); - - .copy { - float: left; - width: flex-grid(8,12); - margin-right: flex-gutter(); - } - - .nav-introduction-supplementary { - @extend .t-copy-sub2; - float: right; - width: flex-grid(4,12); - display: block; - text-align: right; - - .icon { - @extend .t-action3; - display: inline-block; - vertical-align: middle; - margin-right: ($baseline/4); - } - } - } - } } .content-primary, .content-supplementary { diff --git a/cms/static/sass/elements/_system-help.scss b/cms/static/sass/elements/_system-help.scss index d1b0584fc4..ca8fa38154 100644 --- a/cms/static/sass/elements/_system-help.scss +++ b/cms/static/sass/elements/_system-help.scss @@ -1,40 +1,169 @@ // studio - elements - system help // ==================== -// notices - in-context: to be used as notices to users within the context of a form/action -.notice-incontext { - @extend .ui-well; - border-radius: ($baseline/10); +// view introductions - common greeting/starting points for the UI +.content .introduction { + @include box-sizing(border-box); + margin-bottom: $baseline; .title { - @extend .t-title7; - margin-bottom: ($baseline/4); + @extend .t-title4; font-weight: 600; } .copy { @extend .t-copy-sub1; - @include transition(opacity $tmg-f2 ease-in-out 0s); - opacity: 0.75; } strong { font-weight: 600; } - &:hover { + // CASE: has links alongside + &.has-links { + @include clearfix(); .copy { - opacity: 1.0; + float: left; + width: flex-grid(8,12); + margin-right: flex-gutter(); + } + + .nav-introduction-supplementary { + @extend .t-copy-sub2; + float: right; + width: flex-grid(4,12); + display: block; + text-align: right; + + .icon { + @extend .t-action3; + display: inline-block; + vertical-align: middle; + margin-right: ($baseline/4); + } } } } -// particular warnings around a workflow for something +// notices - in-context: to be used as notices to users within the context of a form/action +.notice-incontext { + @extend .ui-well; + border-radius: ($baseline/10); + position: relative; + overflow: hidden; + margin-bottom: $baseline; + + .title { + @extend .t-title7; + margin-bottom: ($baseline/4); + font-weight: 700; + } + + .copy { + @extend .t-copy-sub1; + @include transition(opacity $tmg-f2 ease-in-out 0s); + opacity: 0.75; + margin-bottom: $baseline; + + &:last-child { + margin-bottom: 0; + } + } + + strong { + font-weight: 600; + } + + &.has-status { + + .status-indicator { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: ($baseline/4); + opacity: 0.40; + } + } + + // CASE: notice has actions { + &.has-actions { + + .list-actions { + margin-top: ($baseline*0.75); + + .action-item { + + } + + .action-primary { + @extend .btn-primary-blue; + @extend .t-action3; + } + } + } +} + +// particular notice - warnings around a workflow for something .notice-workflow { background: $yellow-l5; - .copy { + .status-indicator { + background: $yellow; + } + + title { color: $gray-d1; } + + .copy { + color: $gray; + } +} + +// particular notice - instructional +.notice-instruction { + background-color: $gray-l4; + + .title { + color: $gray-d1; + } + + .copy { + color: $gray; + } + + &.has-actions { + + .list-actions { + + .action-item { + + } + + .action-primary { + @extend .btn-primary-blue; + @extend .t-action3; + } + } + } +} + +// particular notice - confirmation +.notice-confirmation { + background-color: $green-l5; + + .status-indicator { + background: $green-s1; + } + + .title { + color: $green; + } + + .copy { + color: $gray; + } } diff --git a/cms/static/sass/views/_dashboard.scss b/cms/static/sass/views/_dashboard.scss index 8d1b068256..a1cd5a47f6 100644 --- a/cms/static/sass/views/_dashboard.scss +++ b/cms/static/sass/views/_dashboard.scss @@ -3,8 +3,254 @@ body.dashboard { + // temp + .content { + margin-bottom: ($baseline*5); + + &:last-child { + margin-bottom: 0; + } + } + + // ==================== + + // basic layout + .content-primary, .content-supplementary { + @include box-sizing(border-box); + float: left; + } + + .content-primary { + width: flex-grid(9, 12); + margin-right: flex-gutter(); + } + + .content-supplementary { + width: flex-grid(3, 12); + } + + // ==================== + + // elements - notices + .content .notice-incontext { + width: flexgrid(9, 9); + + // CASE: notice has actions { + &.has-actions { + + .msg, .list-actions { + display: inline-block; + vertical-align: middle; + } + + .msg { + width: flex-grid(6, 9); + margin-right: flex-gutter(); + } + + .list-actions { + width: flex-grid(3, 9); + text-align: right; + margin-top: 0; + + .action-item { + + } + + .action-create-course { + @extend .btn-primary-green; + } + } + } + } + + + + // elements - authorship controls + .wrapper-authorshiprights { + overflow: hidden; + + .ui-toggle-control { + @extend .depth2; + @extend .btn-secondary-gray; + @include clearfix(); + display: block; + text-align: left; + + // STATE: hover - syncing up colors with current so transition is smoother + &:hover { + background: $gray-d1; + color: $white; + } + + .label { + @extend .t-action3; + float: left; + width: flex-grid(8, 9); + margin: 3px flex-gutter() 0 0; + } + + .icon-remove-sign { + @extend .t-action1; + @include transition(rotate 10.0s ease-in-out 0s); + @include transform(rotate(45deg)); + @include transform-origin(center center); + float: right; + text-align: right; + } + } + + .ui-toggle-target { + @extend .depth1; + @include transition(opacity 0.50s ease-in-out 0s); + position: relative; + top: -2px; + display: none; + opacity: 0; + } + + // CASE: when the content area is shown + &.is-shown { + + .ui-toggle-control { + @include border-bottom-radius(0); + + .icon-remove-sign { + @include transform(rotate(90deg)); + @include transform-origin(center center); + } + } + + .ui-toggle-target { + display: block; + opacity: 1.0; + } + } + + + } + + // elements - authorship controls + .status-authorship { + margin-top: $baseline; + + .title { + @extend .t-title7; + margin-bottom: ($baseline/4); + font-weight: 700; + color: $gray-d1; + } + + .copy { + + } + + .list-actions { + margin-top: ($baseline*0.75); + + .action-item { + + } + + .action-primary { + @extend .btn-primary-blue; + @extend .t-action3; + } + } + + .status-update { + + .label { + @extend .text-sr; + } + + .value { + @include border-radius(($baseline/4)); + position: relative; + overflow: hidden; + padding: ($baseline/5) ($baseline/2); + background: $gray; + + .status-indicator { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: ($baseline/4); + opacity: 0.40; + } + } + + .value-formal, .value-description { + @include border-radius(($baseline/10)); + display: inline-block; + vertical-align: middle; + color: $white; + } + + .value-formal { + @extend .t-title5; + margin: ($baseline/2); + font-weight: 700; + + [class^="icon-"] { + margin-right: ($baseline/4); + } + } + + .value-description { + @extend .t-copy-sub1; + position: relative; + color: $white; + opacity: 0.65; + } + } + + // CASE: rights are not requested yet + &.is-unrequested { + + .title { + @extend .text-sr; + } + } + + // CASE: status is pending + &.is-pending { + + .status-update { + + .value { + background: $orange; + } + + .status-indicator { + background: $orange-d1; + } + } + } + + + // CASE: status is rejected + &.is-rejected { + + .status-update { + + .value { + background: $red-l1; + } + + .status-indicator { + background: $red-s1; + } + } + } + } + + // ==================== + .my-classes { - margin-top: $baseline; + margin: $baseline 0; } .class-list { diff --git a/cms/templates/index.html b/cms/templates/index.html index 5a1003b391..21cbc26799 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -5,6 +5,22 @@ <%block name="title">${_("My Courses")} <%block name="bodyclass">is-signedin index dashboard +<%block name="jsextra"> + + + + <%block name="header_extras"> - - <%block name="header_extras"> + + <%block name="title">${_("My Courses")} <%block name="bodyclass">is-signedin index dashboard @@ -45,13 +61,12 @@ @@ -65,18 +80,18 @@
- - - - - -

${_("Welcome, %(name)s!") % dict(name= user.username)}

-

${_("Here are all of the courses you currently have access to in Studio:")}

+ %if len(courses) > 0: +

${_("Here are all of the courses you currently have access to in Studio:")}

+ %else: + +

${_("Sorry, you don't have any courses!")}

+ %endif
+
@@ -103,20 +118,21 @@

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while Course Authorship privileges are granted by edX. Our team will evaluate your request and provide you feedback within 24 hours during the work week.')}

- !-- if request is unrequested --> - %if course_creator_status = "unrequested": + + %if course_creator_status == "unrequested": - !-- if request is pending --> - %elif course_creator_status = "pending": + + %elif course_creator_status == "pending":

${_('Your Authorship Request Status:')}

@@ -130,8 +146,8 @@
- !-- if request is denied --> - %elif course_creator_status = "denied": + + %elif course_creator_status == "denied":

${_('Your Authorship Request Status:')}

@@ -174,14 +190,14 @@ % endif - % if not disable_course_creation and course_creator_status = "unrequested": + % if not disable_course_creation and course_creator_status == "unrequested":

${_('Can I create courses in Studio?')}

${_('In order to create courses in Studio, you must have authorship rights to create your own course.')}

- % elif not disable_course_creation and course_creator_status = "denied": + % elif not disable_course_creation and course_creator_status == "denied":

${_('Can I create courses in Studio?')}

${_('Your request to author courses in studio has been denied. Please')} ${_('contact edX Staff with further questions')}

@@ -204,7 +220,7 @@

${_('We need to verify your email address')}

-

${_('Almost there! In order to complete your sign up we need you verify your $emailaddress email address. An activation message and next steps should be waiting for you there.')}

+

${_('Almost there! In order to complete your sign up we need you verify your email address (%(email)s). An activation message and next steps should be waiting for you there.') % dict(email=user.email)}

From 135d10d4c554114a33838937c3d50c681e5b9ec6 Mon Sep 17 00:00:00 2001 From: cahrens Date: Wed, 10 Jul 2013 17:26:55 -0400 Subject: [PATCH 15/71] Fix unicode return value. --- cms/djangoapps/course_creators/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/djangoapps/course_creators/models.py b/cms/djangoapps/course_creators/models.py index 607dae4af2..31bcbc5611 100644 --- a/cms/djangoapps/course_creators/models.py +++ b/cms/djangoapps/course_creators/models.py @@ -39,7 +39,7 @@ class CourseCreator(models.Model): "why course creation access was denied)")) def __unicode__(self): - return u'%str | %str [%str] | %str' % (self.user, self.state, self.state_changed, self.note) + return u'%s | %s [%s]' % (self.user, self.state, self.state_changed) @receiver(post_init, sender=CourseCreator) From aaa67b28a9f86e0ba206ddd44464ee9cd0f29d27 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 11 Jul 2013 11:10:54 -0400 Subject: [PATCH 16/71] Studio: revises template logic, html, and styling for Dashboard UI states --- cms/static/sass/_base.scss | 18 ++ cms/static/sass/elements/_system-help.scss | 20 +- cms/static/sass/views/_dashboard.scss | 19 +- cms/templates/index.html | 247 +++++++++++++-------- 4 files changed, 195 insertions(+), 109 deletions(-) diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index fe107d7511..e9b01509fe 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -446,6 +446,24 @@ p, ul, ol, dl { } } + // actions + .list-actions { + @extend .no-list; + + .action-item { + margin-bottom: ($baseline/4); + border-bottom: 1px dotted $gray-l4; + padding-bottom: ($baseline/4); + + + &:last-child { + margin-bottom: 0; + border: none; + padding-bottom: 0; + } + } + } + // navigation .nav-related, .nav-page { diff --git a/cms/static/sass/elements/_system-help.scss b/cms/static/sass/elements/_system-help.scss index ca8fa38154..0f90d9db5c 100644 --- a/cms/static/sass/elements/_system-help.scss +++ b/cms/static/sass/elements/_system-help.scss @@ -104,6 +104,22 @@ } } } + + // list of notices all in one + &.list-notices { + + .notice-item { + margin-bottom: $baseline; + border-bottom: 1px solid $gray-l3; + padding-bottom: $baseline; + + &:last-child { + margin-bottom: 0; + border: none; + padding-bottom: 0; + } + } + } } // particular notice - warnings around a workflow for something @@ -128,11 +144,11 @@ background-color: $gray-l4; .title { - color: $gray-d1; + color: $gray-d3; } .copy { - color: $gray; + color: $gray-d2; } &.has-actions { diff --git a/cms/static/sass/views/_dashboard.scss b/cms/static/sass/views/_dashboard.scss index 7132874c14..b00306e5c5 100644 --- a/cms/static/sass/views/_dashboard.scss +++ b/cms/static/sass/views/_dashboard.scss @@ -36,7 +36,7 @@ body.dashboard { width: flexgrid(9, 9); // CASE: notice has actions { - &.has-actions { + &.has-actions, &.list-notices .notice-item.has-actions { .msg, .list-actions { display: inline-block; @@ -59,6 +59,7 @@ body.dashboard { .action-create-course { @extend .btn-primary-green; + @extend .t-action3; } } } @@ -66,8 +67,8 @@ body.dashboard { - // elements - authorship controls - .wrapper-authorshiprights { + // elements - course creation rights controls + .wrapper-creationrights { overflow: hidden; .ui-toggle-control { @@ -129,8 +130,8 @@ body.dashboard { } - // elements - authorship controls - .status-authorship { + // elements - course creation rights controls + .status-creationrights { margin-top: $baseline; .title { @@ -144,7 +145,7 @@ body.dashboard { } - .list-actions { + .list-actions, .form-actions { margin-top: ($baseline*0.75); .action-item { @@ -253,10 +254,10 @@ body.dashboard { } .class-list { - margin-top: 20px; + margin-top: $baseline; border-radius: 3px; - border: 1px solid $darkGrey; - background: #fff; + border: 1px solid $gray-d2; + background: $white; box-shadow: 0 1px 2px rgba(0, 0, 0, .1); li { diff --git a/cms/templates/index.html b/cms/templates/index.html index 0f10758dd6..4166542f35 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -2,22 +2,6 @@ <%inherit file="base.html" /> -<%block name="jsextra"> - - - - <%block name="title">${_("My Courses")} <%block name="bodyclass">is-signedin index dashboard @@ -50,6 +34,19 @@ +<%block name="jsextra"> + + + <%block name="content">
@@ -60,13 +57,12 @@

${_("Page Actions")}

@@ -75,7 +71,6 @@
- % if user.is_active:
@@ -83,86 +78,146 @@

${_("Welcome, %(name)s!") % dict(name= user.username)}

+ %if len(courses) > 0:
- %if len(courses) > 0: -

${_("Here are all of the courses you currently have access to in Studio:")}

- %else: - -

${_("Sorry, you don't have any courses!")}

- %endif +

${_("Here are all of the courses you currently have access to in Studio:")}

+ %else: +
+

${_("You currently aren't associated with any Studio Courses.")}

+
+ %endif
-
-
    - %for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower() if s[0] is not None else ''): -
  • - - ${course} - - View Live -
  • - %endfor -
+ %if len(courses) > 0: +
+
+
    + %for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower() if s[0] is not None else ''): +
  • + + ${course} + + View Live +
  • + %endfor +
+
+ %else: +
+
+
+

${_("Are you staff on an existing Studio course?")}

+
+

${_('You will need to be added to the course in Studio by the Course Creator. Please get in touch with the course creator or administrator for the specific course you are helping to author.')}

+
+
+
+ + %if not disable_course_creation and course_creator_status == "granted": +
+
+

${_('Create Your First Course')}

+
+

${_('Your new course is just a click away!')}

+
+
+ + +
+ % endif + +
+ % endif + % if not disable_course_creation and course_creator_status != "granted": -
-

- ${_('Becoming a Course Author in Studio')} -

-
-
-

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while Course Authorship privileges are granted by edX. Our team will evaluate your request and provide you feedback within 24 hours during the work week.')}

+ %if course_creator_status == "unrequested": +
+

+ ${_('Becoming a Course Creator in Studio')} +

+ +
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while Course Creator privileges are granted by edX. Our team will evaluate your request and provide you feedback within 24 hours during the work week.')}

+
+ +
+

${_('Your Course Creator Request Status:')}

+ +
+ + + +
+ +
+
+
- - - %if course_creator_status == "unrequested": -
-

${_('Your Authorship Request Status:')}

- - -
- - - %elif course_creator_status == "pending": -
-

${_('Your Authorship Request Status:')}

- -
-
${_('Your authorship request is:')}
-
- - ${_('Pending')} - ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} -
-
-
- - - %elif course_creator_status == "denied": -
-

${_('Your Authorship Request Status:')}

- -
-
${_('Your authorship request is:')}
-
- - ${_('Denied')} - ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} -
-
-
- % endif
-
+ + %elif course_creator_status == "denied": +
+

+ ${_('Your Course Creator Request Status')} +

+ +
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while Course Creator privileges are granted by edX. Our team is has completed evaluating your request.')}

+
+ +
+

${_('Your Course Creator Request Status:')}

+ +
+
${_('Your Course Creator request is:')}
+
+ + ${_('Denied')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+
+
+
+ + %elif course_creator_status == "pending": +
+

+ ${_('Your Course Creator Request Status')} +

+ +
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while Course Creator privileges are granted by edX. Our team is currently evaluating your request.')}

+
+ +
+

${_('Your Course Creator Request Status:')}

+ +
+
${_('Your Course Creator request is:')}
+
+ + ${_('Pending')} + ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} +
+
+
+
+
+ % endif + % endif
@@ -181,7 +236,6 @@
- % if disable_course_creation and settings.MITX_FEATURES.get('STAFF_EMAIL',''):

${_('Can I create courses in Studio?')}

@@ -189,14 +243,12 @@
% endif - % if not disable_course_creation and course_creator_status == "unrequested":

${_('Can I create courses in Studio?')}

-

${_('In order to create courses in Studio, you must have authorship rights to create your own course.')}

+

${_('In order to create courses in Studio, you must have Course Creator privileges to create your own course.')}

- % elif not disable_course_creation and course_creator_status == "denied":

${_('Can I create courses in Studio?')}

@@ -208,7 +260,6 @@
- % else:
From 51c9523f979a0916d3cb89b4c17e765b2ab2a1b6 Mon Sep 17 00:00:00 2001 From: cahrens Date: Thu, 11 Jul 2013 12:36:30 -0400 Subject: [PATCH 17/71] Only send a single variable to index.html about authorship rights. --- cms/templates/index.html | 241 +++++++++++++++++++++++++-------------- 1 file changed, 153 insertions(+), 88 deletions(-) diff --git a/cms/templates/index.html b/cms/templates/index.html index 1df0878bd0..3b7b2d74b4 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -34,6 +34,19 @@ +<%block name="jsextra"> + + + <%block name="content">
@@ -44,12 +57,10 @@

${_("Page Actions")}

- % if user.is_active:
- - - - - -

${_("Welcome, %(name)s!") % dict(name= user.username)}

+ %if len(courses) > 0:

${_("Here are all of the courses you currently have access to in Studio:")}

+ + %else: +
+

${_("You currently aren't associated with any Studio Courses.")}

+
+ %endif
-
-
    - %for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower() if s[0] is not None else ''): -
  • - - ${course} - - View Live -
  • - %endfor -
-
- - % if not disable_course_creation and course_creator_status != "granted": -
-

- ${_('Becoming a Course Author in Studio')} -

- -
-
-

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while Course Authorship privileges are granted by edX. Our team will evaluate your request and provide you feedback within 24 hours during the work week.')}

-
- - !-- if request is unrequested --> - %if course_creator_status = "unrequested": -
-

${_('Your Authorship Request Status:')}

- - -
- - !-- if request is pending --> - %elif course_creator_status = "pending": -
-

${_('Your Authorship Request Status:')}

- -
-
${_('Your authorship request is:')}
-
- - ${_('Pending')} - ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} -
-
-
- - !-- if request is denied --> - %elif course_creator_status = "denied": -
-

${_('Your Authorship Request Status:')}

- -
-
${_('Your authorship request is:')}
-
- - ${_('Denied')} - ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} -
-
-
- % endif + %if len(courses) > 0: +
+
+
    + %for course, url, lms_link in sorted(courses, key=lambda s: s[0].lower() if s[0] is not None else ''): +
  • + + ${course} + + View Live +
  • + %endfor +
+ + %else: +
+
+
+

${_("Are you staff on an existing Studio course?")}

+
+

${_('You will need to be added to the course in Studio by the course creator. Please get in touch with the course creator or administrator for the specific course you are helping to author.')}

+
+
+
+ + %if course_creator_status == "granted": +
+
+

${_('Create Your First Course')}

+
+

${_('Your new course is just a click away!')}

+
+
+ + +
+ % endif + +
% endif + + + %if course_creator_status == "unrequested": +
+

+ ${_('Becoming a Course Creator in Studio')} +

+ +
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team will evaluate your request and provide you feedback within 24 hours during the work week.')}

+
+ +
+

${_('Your Course Creator Request Status:')}

+ +
+ + + +
+ +
+
+
+
+
+ + %elif course_creator_status == "denied": +
+

+ ${_('Your Course Creator Request Status')} +

+ +
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team is has completed evaluating your request.')}

+
+ +
+

${_('Your Course Creator Request Status:')}

+ +
+
${_('Your Course Creator request is:')}
+
+ + ${_('Denied')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+
+
+
+ + %elif course_creator_status == "pending": +
+

+ ${_('Your Course Creator Request Status')} +

+ +
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team is currently evaluating your request.')}

+
+ +
+

${_('Your Course Creator Request Status:')}

+ +
+
${_('Your Course Creator request is:')}
+
+ + ${_('Pending')} + ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} +
+
+
+
+
+ % endif +
- - % if disable_course_creation and settings.MITX_FEATURES.get('STAFF_EMAIL',''): + % if course_creator_status=='disallowed_for_this_site' and settings.MITX_FEATURES.get('STAFF_EMAIL',''):

${_('Can I create courses in Studio?')}

${_('In order to create courses in Studio, you must')} ${_("contact edX staff to help you create a course")}

% endif - - % if not disable_course_creation and course_creator_status = "unrequested": + % if course_creator_status == "unrequested":

${_('Can I create courses in Studio?')}

-

${_('In order to create courses in Studio, you must have authorship rights to create your own course.')}

+

${_('In order to create courses in Studio, you must have course creator privileges to create your own course.')}

- - % elif not disable_course_creation and course_creator_status = "denied": + % elif course_creator_status == "denied":

${_('Can I create courses in Studio?')}

${_('Your request to author courses in studio has been denied. Please')} ${_('contact edX Staff with further questions')}

@@ -192,7 +258,6 @@
- % else:
@@ -204,7 +269,7 @@

${_('We need to verify your email address')}

-

${_('Almost there! In order to complete your sign up we need you verify your $emailaddress email address. An activation message and next steps should be waiting for you there.')}

+

${_('Almost there! In order to complete your sign up we need you verify your email address (%(email)s). An activation message and next steps should be waiting for you there.') % dict(email=user.email)}

From 3715d6c429865f4ef6e869daff1a10f7b9d12fe8 Mon Sep 17 00:00:00 2001 From: cahrens Date: Thu, 11 Jul 2013 12:39:51 -0400 Subject: [PATCH 18/71] Only send a single variable to index.html about authorship rights. --- cms/djangoapps/contentstore/views/user.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py index dae0d246a5..dbb2fbddb6 100644 --- a/cms/djangoapps/contentstore/views/user.py +++ b/cms/djangoapps/contentstore/views/user.py @@ -11,6 +11,7 @@ from contentstore.utils import get_url_reverse, get_lms_link_for_item from util.json_request import expect_json, JsonResponse from auth.authz import STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME, 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 course_creators.views import get_course_creator_status, add_user_with_status_unrequested from .access import has_access @@ -32,6 +33,18 @@ def index(request): and course.location.name != '') courses = filter(course_filter, courses) + if settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False): + course_creator_status = 'granted' if request.user.is_staff else 'disallowed_for_this_site' + elif settings.MITX_FEATURES.get('ENABLE_CREATOR_GROUP', False): + course_creator_status = get_course_creator_status(request.user) + if course_creator_status is None: + # User not grandfathered in as an existing user, has not previously visited the dashboard page. + # Add the user to the course creator admin table with status 'unrequested'. + add_user_with_status_unrequested(request.user) + course_creator_status = get_course_creator_status(request.user) + else: + course_creator_status = 'granted' + return render_to_response('index.html', { 'new_course_template': Location('i4x', 'edx', 'templates', 'course', 'Empty'), 'courses': [(course.display_name, @@ -39,7 +52,7 @@ def index(request): get_lms_link_for_item(course.location, course_id=course.location.course_id)) for course in courses], 'user': request.user, - 'disable_course_creation': settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff + 'course_creator_status': course_creator_status }) From 59850cb4a3a24e6289222926628de7f4d751856c Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 11 Jul 2013 12:44:13 -0400 Subject: [PATCH 19/71] Studio: cleans up new course form and course listings display --- cms/static/js/base.js | 6 ++-- cms/static/sass/views/_dashboard.scss | 43 ++++++++++++++++----------- cms/templates/index.html | 25 ++++++++-------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index c2f401c77e..a6218541ef 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -577,10 +577,10 @@ function cancelNewSection(e) { function addNewCourse(e) { e.preventDefault(); - $(e.target).hide(); + $(e.target).addClass('disabled'); var $newCourse = $($('#new-course-template').html()); var $cancelButton = $newCourse.find('.new-course-cancel'); - $('.inner-wrapper').prepend($newCourse); + $('.courses').prepend($newCourse); $newCourse.find('.new-course-name').focus().select(); $newCourse.find('form').bind('submit', saveNewCourse); $cancelButton.bind('click', cancelNewCourse); @@ -627,7 +627,7 @@ function saveNewCourse(e) { function cancelNewCourse(e) { e.preventDefault(); - $('.new-course-button').show(); + $('.new-course-button').removeClass('disabled'); $(this).parents('section.new-course').remove(); } diff --git a/cms/static/sass/views/_dashboard.scss b/cms/static/sass/views/_dashboard.scss index b00306e5c5..a2a3c72fb1 100644 --- a/cms/static/sass/views/_dashboard.scss +++ b/cms/static/sass/views/_dashboard.scss @@ -203,7 +203,7 @@ body.dashboard { @extend .t-copy-sub1; position: relative; color: $white; - opacity: 0.65; + opacity: 0.85; } } @@ -249,20 +249,21 @@ body.dashboard { // ==================== - .my-classes { + // course listings + .courses { margin: $baseline 0; } - .class-list { + .list-courses { margin-top: $baseline; border-radius: 3px; - border: 1px solid $gray-d2; + border: 1px solid $gray; background: $white; - box-shadow: 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: 0 1px 2px $shadow-l1; - li { + .course-item { position: relative; - border-bottom: 1px solid $mediumGrey; + border-bottom: 1px solid $gray-l1; &:last-child { border-bottom: none; @@ -302,7 +303,7 @@ body.dashboard { .view-live-button { z-index: 10000; position: absolute; - top: 15px; + top: ($baseline*0.75); right: $baseline; padding: ($baseline/4) ($baseline/2); opacity: 0.0; @@ -316,17 +317,25 @@ body.dashboard { } .new-course { - padding: 15px 25px; - margin-top: 20px; + @include clearfix(); + padding: ($baseline*0.75) ($baseline*1.25); + margin-top: $baseline; border-radius: 3px; - border: 1px solid $darkGrey; - background: #fff; + border: 1px solid $gray; + background: $white; box-shadow: 0 1px 2px rgba(0, 0, 0, .1); - @include clearfix; + + .title { + @extend .t-title4; + font-weight: 600; + margin-bottom: ($baseline/2); + border-bottom: 1px solid $gray-l3; + padding-bottom: ($baseline/2); + } .row { - margin-bottom: 15px; - @include clearfix; + @include clearfix(); + margin-bottom: ($baseline*0.75); } .column { @@ -343,8 +352,8 @@ body.dashboard { } label { + @extend .t-title7; display: block; - font-size: 13px; font-weight: 700; } @@ -355,7 +364,7 @@ body.dashboard { } .new-course-name { - font-size: 19px; + @extend .t-title5; font-weight: 300; } diff --git a/cms/templates/index.html b/cms/templates/index.html index 3b7b2d74b4..74831d1984 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -8,6 +8,7 @@ <%block name="header_extras"> + + <%block name="header_extras"> @@ -151,10 +158,8 @@

${_('Your Course Creator Request Status:')}

-
- - - + +
diff --git a/cms/urls.py b/cms/urls.py index 56efd1a557..67314178f8 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -12,6 +12,7 @@ admin.autodiscover() urlpatterns = ('', # nopep8 url(r'^$', 'contentstore.views.howitworks', name='homepage'), url(r'^listing', 'contentstore.views.index', name='index'), + url(r'^request_course_creator$', 'contentstore.views.request_course_creator', name='request_course_creator'), url(r'^edit/(?P.*?)$', 'contentstore.views.edit_unit', name='edit_unit'), url(r'^subsection/(?P.*?)$', 'contentstore.views.edit_subsection', name='edit_subsection'), url(r'^preview_component/(?P.*?)$', 'contentstore.views.preview_component', name='preview_component'), From b4457371a86713051a3c3c72669d3c1e28bc47e7 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 11 Jul 2013 14:53:02 -0400 Subject: [PATCH 22/71] Studio: revises and syncs up activation message UI with dashboard UI --- cms/templates/activation_active.html | 35 +++++++++++++++------ cms/templates/activation_complete.html | 35 ++++++++++++++++----- cms/templates/activation_invalid.html | 43 +++++++++++++++++++------- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/cms/templates/activation_active.html b/cms/templates/activation_active.html index 712c73abf9..51c01f1155 100644 --- a/cms/templates/activation_active.html +++ b/cms/templates/activation_active.html @@ -1,14 +1,31 @@ <%inherit file="base.html" /> <%block name="content"> - -
-
- -
-

Account already active!

-

This account has already been activated. Log in here.

+
+
+

${_("Studio Account Activation")}

+
-
- \ No newline at end of file +
+
+
+
+ +
+
+

${_("Your account is already active")}

+
+

${_("This account, set up using (%(email)s), has already been activated. Please sign in to start working within edX Studio.") % dict(email=user.email)}

+
+
+ + +
+
+
+ diff --git a/cms/templates/activation_complete.html b/cms/templates/activation_complete.html index 1e195a632c..d6bdab36dc 100644 --- a/cms/templates/activation_complete.html +++ b/cms/templates/activation_complete.html @@ -1,12 +1,33 @@ <%inherit file="base.html" /> <%block name="content"> - -
-
-

Activation Complete!

-

Thanks for activating your account. Log in here.

+
+
+

${_("Studio Account Activation")}

+
-
- \ No newline at end of file +
+
+
+
+ +
+
+

${_("Your account activation is complete!")}

+
+

${_("Thank you for activating your account. You may now sign in and start using edX Studio to author courses.")}

+
+
+ + +
+
+
+ + + diff --git a/cms/templates/activation_invalid.html b/cms/templates/activation_invalid.html index c4eb16875b..0c663aa307 100644 --- a/cms/templates/activation_invalid.html +++ b/cms/templates/activation_invalid.html @@ -1,16 +1,35 @@ <%inherit file="base.html" /> <%block name="content"> -
-
-

Activation Invalid

- -

Something went wrong. Check to make sure the URL you went to was - correct -- e-mail programs will sometimes split it into two - lines. If you still have issues, e-mail us to let us know what happened - at bugs@mitx.mit.edu.

- -

Or you can go back to the home page.

+
+
+

${_("Studio Account Activation")}

+
-
- \ No newline at end of file + +
+
+
+
+ +
+
+

${_('Your account activation is invalid')}

+
+

${_("We're sorry. Something went wrong with your activation. Check to make sure the URL you went to was correct — e-mail programs will sometimes split it into two lines.")}

+

${_("If you still have issues, contact edX Support. In the meatime, you can also return to")} {_('the Studio homepage.')}

+
+
+ + +
+
+
+ + + + From c360faed5893c276a409093c648ec4789f094d0c Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 11 Jul 2013 16:30:30 -0400 Subject: [PATCH 23/71] Studio: revises dashboard course creation button and form interaction --- cms/static/js/base.js | 2 +- cms/templates/index.html | 121 ++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index a6218541ef..5042764212 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -576,7 +576,7 @@ function cancelNewSection(e) { function addNewCourse(e) { e.preventDefault(); - + $('.new-course-button').addClass('disabled'); $(e.target).addClass('disabled'); var $newCourse = $($('#new-course-template').html()); var $cancelButton = $newCourse.find('.new-course-cancel'); diff --git a/cms/templates/index.html b/cms/templates/index.html index d182f4c174..dd36f19b44 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -113,6 +113,9 @@
%else: +
+
+
@@ -144,82 +147,82 @@ % endif - %if course_creator_status == "unrequested": -
-

- ${_('Becoming a Course Creator in Studio')} -

+ %if course_creator_status == "unrequested": +
+

+ ${_('Becoming a Course Creator in Studio')} +

-
-
-

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team will evaluate your request and provide you feedback within 24 hours during the work week.')}

-
+
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team will evaluate your request and provide you feedback within 24 hours during the work week.')}

+
-
-

${_('Your Course Creator Request Status:')}

+
+

${_('Your Course Creator Request Status:')}

- - -
- -
- +
+ +
+ +
+
+
-
- %elif course_creator_status == "denied": -
-

- ${_('Your Course Creator Request Status')} -

+ %elif course_creator_status == "denied": +
+

+ ${_('Your Course Creator Request Status')} +

-
-
-

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team is has completed evaluating your request.')}

-
+
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team is has completed evaluating your request.')}

+
-
-

${_('Your Course Creator Request Status:')}

+
+

${_('Your Course Creator Request Status:')}

-
-
${_('Your Course Creator request is:')}
-
- - ${_('Denied')} - ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} -
-
+
+
${_('Your Course Creator request is:')}
+
+ + ${_('Denied')} + ${_('Your request did not meet the criteria/guidelines specified by edX Staff.')} +
+
+
-
- %elif course_creator_status == "pending": -
-

- ${_('Your Course Creator Request Status')} -

+ %elif course_creator_status == "pending": +
+

+ ${_('Your Course Creator Request Status')} +

-
-
-

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team is currently evaluating your request.')}

-
+
+
+

${_('edX Studio is a hosted solution for our xConsortium partners and selected guests. Courses for which you are a team member appear above for you to edit, while course creator privileges are granted by edX. Our team is currently evaluating your request.')}

+
-
-

${_('Your Course Creator Request Status:')}

+
+

${_('Your Course Creator Request Status:')}

-
-
${_('Your Course Creator request is:')}
-
- - ${_('Pending')} - ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} -
-
+
+
${_('Your Course Creator request is:')}
+
+ + ${_('Pending')} + ${_('Your request is currently being reviewed by edX staff and should be updated shortly.')} +
+
+
-
- % endif + % endif From 4ecceb292e272bba960b445adea15a6230112e74 Mon Sep 17 00:00:00 2001 From: cahrens Date: Fri, 12 Jul 2013 09:30:36 -0400 Subject: [PATCH 24/71] Add tests for changing table status without staff permissions. --- cms/djangoapps/course_creators/models.py | 9 ++++--- .../course_creators/tests/test_views.py | 25 +++++++++++++------ cms/djangoapps/course_creators/views.py | 15 ++++++----- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/cms/djangoapps/course_creators/models.py b/cms/djangoapps/course_creators/models.py index 7645b21575..b370ac423a 100644 --- a/cms/djangoapps/course_creators/models.py +++ b/cms/djangoapps/course_creators/models.py @@ -54,20 +54,23 @@ def post_init_callback(sender, **kwargs): @receiver(post_save, sender=CourseCreator) def post_save_callback(sender, **kwargs): """ - Extend to update state_changed time and modify the course creator group in authz.py. + Extend to update state_changed time and fire event to update course creator group, if appropriate. """ instance = kwargs['instance'] # We only wish to modify the state_changed time if the state has been modified. We don't wish to # modify it for changes to the notes field. if instance.state != instance.orig_state: - if hasattr(instance, 'admin'): + # If either old or new state is 'granted', we must manipulate the course creator + # group maintained by authz. That requires staff permissions (stored admin). + if instance.state == CourseCreator.GRANTED or instance.orig_state == CourseCreator.GRANTED: + assert hasattr(instance, 'admin'), 'Must have stored staff user to change course creator group' update_creator_state.send( sender=sender, caller=instance.admin, user=instance.user, add=instance.state == CourseCreator.GRANTED ) - # TODO: Else must be sure that state change does not switch to or from granted + instance.state_changed = timezone.now() instance.orig_state = instance.state instance.save() diff --git a/cms/djangoapps/course_creators/tests/test_views.py b/cms/djangoapps/course_creators/tests/test_views.py index bd91208b9c..943120f3c0 100644 --- a/cms/djangoapps/course_creators/tests/test_views.py +++ b/cms/djangoapps/course_creators/tests/test_views.py @@ -7,7 +7,7 @@ from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from course_creators.views import add_user_with_status_unrequested, add_user_with_status_granted -from course_creators.views import get_course_creator_status, update_course_creator_group +from course_creators.views import get_course_creator_status, update_course_creator_group, user_requested_access from course_creators.models import CourseCreator from auth.authz import is_user_in_creator_group import mock @@ -26,14 +26,11 @@ class CourseCreatorView(TestCase): def test_staff_permission_required(self): """ - Tests that add methods and course creator group method must be called with staff permissions. + Tests that any method changing the course creator authz group must be called with staff permissions. """ with self.assertRaises(PermissionDenied): add_user_with_status_granted(self.user, self.user) - with self.assertRaises(PermissionDenied): - add_user_with_status_unrequested(self.user, self.user) - with self.assertRaises(PermissionDenied): update_course_creator_group(self.user, self.user, True) @@ -41,7 +38,7 @@ class CourseCreatorView(TestCase): self.assertIsNone(get_course_creator_status(self.user)) def test_add_unrequested(self): - add_user_with_status_unrequested(self.admin, self.user) + add_user_with_status_unrequested(self.user) self.assertEqual('unrequested', get_course_creator_status(self.user)) # Calling add again will be a no-op (even if state is different). @@ -57,7 +54,7 @@ class CourseCreatorView(TestCase): self.assertEqual('granted', get_course_creator_status(self.user)) # Calling add again will be a no-op (even if state is different). - add_user_with_status_unrequested(self.admin, self.user) + add_user_with_status_unrequested(self.user) self.assertEqual('granted', get_course_creator_status(self.user)) self.assertTrue(is_user_in_creator_group(self.user)) @@ -69,3 +66,17 @@ class CourseCreatorView(TestCase): self.assertTrue(is_user_in_creator_group(self.user)) update_course_creator_group(self.admin, self.user, False) self.assertFalse(is_user_in_creator_group(self.user)) + + def test_user_requested_access(self): + add_user_with_status_unrequested(self.user) + self.assertEqual('unrequested', get_course_creator_status(self.user)) + user_requested_access(self.user) + self.assertEqual('pending', get_course_creator_status(self.user)) + + def test_user_requested_already_granted(self): + add_user_with_status_granted(self.admin, self.user) + self.assertEqual('granted', get_course_creator_status(self.user)) + # Will not "downgrade" to pending because that would require removing the + # user from the authz course creator group (and that can only be done by an admin). + user_requested_access(self.user) + self.assertEqual('granted', get_course_creator_status(self.user)) diff --git a/cms/djangoapps/course_creators/views.py b/cms/djangoapps/course_creators/views.py index 12993d1be4..6b2d91acb2 100644 --- a/cms/djangoapps/course_creators/views.py +++ b/cms/djangoapps/course_creators/views.py @@ -20,10 +20,11 @@ def add_user_with_status_granted(caller, user): """ Adds a user to the course creator table with status 'granted'. - If the user is already in the table, this method is a no-op - (state will not be changed). Caller must have staff permissions. - This method also adds the user to the course creator group maintained by authz.py. + Caller must have staff permissions. + + If the user is already in the table, this method is a no-op + (state will not be changed). """ _add_user(user, CourseCreator.GRANTED) update_course_creator_group(caller, user, True) @@ -64,11 +65,13 @@ def user_requested_access(user): """ User has requested course creator access. - This changes the user state to CourseCreator.PENDING. + This changes the user state to CourseCreator.PENDING, unless the user + state is already CourseCreator.GRANTED, in which case this method is a no-op. """ user = CourseCreator.objects.get(user=user) - user.state = CourseCreator.PENDING - user.save() + if user.state != CourseCreator.GRANTED: + user.state = CourseCreator.PENDING + user.save() def _add_user(user, state): From d417a78c7493630a2f8ed1d701dd7d78ae266689 Mon Sep 17 00:00:00 2001 From: cahrens Date: Fri, 12 Jul 2013 11:25:12 -0400 Subject: [PATCH 25/71] Tests for course creator status as returned in index page. --- .../contentstore/tests/test_users.py | 186 ++++++++++++++++++ cms/djangoapps/contentstore/views/user.py | 50 +++-- 2 files changed, 217 insertions(+), 19 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_users.py b/cms/djangoapps/contentstore/tests/test_users.py index f945ef50fc..bba4d42f4d 100644 --- a/cms/djangoapps/contentstore/tests/test_users.py +++ b/cms/djangoapps/contentstore/tests/test_users.py @@ -1,6 +1,18 @@ +""" +Tests for user.py. +""" import json +import mock from .utils import CourseTestCase from django.core.urlresolvers import reverse +from contentstore.views.user import _get_course_creator_status +from course_creators.views import add_user_with_status_granted +from course_creators.admin import CourseCreatorAdmin +from course_creators.models import CourseCreator + +from django.http import HttpRequest +from django.contrib.auth.models import User +from django.contrib.admin.sites import AdminSite class UsersTestCase(CourseTestCase): @@ -13,3 +25,177 @@ class UsersTestCase(CourseTestCase): self.assertEqual(resp.status_code, 400) content = json.loads(resp.content) self.assertEqual(content["Status"], "Failed") + + +class IndexCourseCreatorTests(CourseTestCase): + """ + Tests the various permutations of course creator status. + """ + def setUp(self): + super(IndexCourseCreatorTests, self).setUp() + + self.index_url = reverse("index") + self.request_access_url = reverse("request_course_creator") + + # Disable course creation takes precedence over enable creator group. I have enabled the + # latter to make this clear. + self.disable_course_creation = { + "DISABLE_COURSE_CREATION": True, + "ENABLE_CREATOR_GROUP": True, + 'STAFF_EMAIL': 'mark@marky.mark', + } + + self.enable_creator_group = {"ENABLE_CREATOR_GROUP": True} + + def test_get_course_creator_status_disable_creation(self): + # DISABLE_COURSE_CREATION is True (this is the case on edx, where we have a marketing site). + # Only edx staff can create courses. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.disable_course_creation): + self.assertTrue(self.user.is_staff) + self.assertEquals('granted', _get_course_creator_status(self.user)) + self._set_user_non_staff() + self.assertFalse(self.user.is_staff) + self.assertEquals('disallowed_for_this_site', _get_course_creator_status(self.user)) + + def test_get_course_creator_status_default_cause(self): + # Neither ENABLE_CREATOR_GROUP nor DISABLE_COURSE_CREATION are enabled. Anyone can create a course. + self.assertEquals('granted', _get_course_creator_status(self.user)) + self._set_user_non_staff() + self.assertEquals('granted', _get_course_creator_status(self.user)) + + def test_get_course_creator_status_creator_group(self): + # ENABLE_CREATOR_GROUP is True. This is the case on edge. + # Only staff members and users who have been granted access can create courses. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): + # Staff members can always create courses. + self.assertEquals('granted', _get_course_creator_status(self.user)) + # Non-staff must request access. + self._set_user_non_staff() + self.assertEquals('unrequested', _get_course_creator_status(self.user)) + # Staff user requests access. + self.client.post(self.request_access_url) + self.assertEquals('pending', _get_course_creator_status(self.user)) + + def test_get_course_creator_status_creator_group_granted(self): + # ENABLE_CREATOR_GROUP is True. This is the case on edge. + # Check return value for a non-staff user who has been granted access. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): + # self.user has staff permissions, can call this method. + add_user_with_status_granted(self.user, self.user) + # now make self.user non-staff + self._set_user_non_staff() + self.assertEquals('granted', _get_course_creator_status(self.user)) + + def test_get_course_creator_status_creator_group_denied(self): + # ENABLE_CREATOR_GROUP is True. This is the case on edge. + # Check return value for a non-staff user who has been denied access. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): + # make self.user non-staff + self._set_user_non_staff() + self._set_user_denied() + self.assertEquals('denied', _get_course_creator_status(self.user)) + + def test_disable_course_creation_enabled_non_staff(self): + # Test index page content when DISABLE_COURSE_CREATION is True, non-staff member. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.disable_course_creation): + self._set_user_non_staff() + self._assert_cannot_create() + + def test_disable_course_creation_enabled_staff(self): + # Test index page content when DISABLE_COURSE_CREATION is True, staff member. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.disable_course_creation): + resp = self._assert_can_create() + self.assertFalse('Email staff to create course' in resp.content) + + def test_can_create_by_default(self): + # Test index page content with neither ENABLE_CREATOR_GROUP nor DISABLE_COURSE_CREATION enabled. + # Anyone can create a course. + self._assert_can_create() + self._set_user_non_staff() + self._assert_can_create() + + def test_course_creator_group_enabled(self): + # Test index page content with ENABLE_CREATOR_GROUP True. + # Staff can always create a course, others must request access. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): + # Staff members can always create courses. + self._assert_can_create() + + # Non-staff case. + self._set_user_non_staff() + resp = self._assert_cannot_create() + self.assertTrue(self.request_access_url in resp.content) + + # Now request access. + self.client.post(self.request_access_url) + + # Still cannot create a course, but the "request access button" is no longer there. + resp = self._assert_cannot_create() + self.assertFalse(self.request_access_url in resp.content) + self.assertTrue('has-status is-pending' in resp.content) + + def test_course_creator_group_granted(self): + # Test index page content with ENABLE_CREATOR_GROUP True, non-staff member with access granted. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): + # self.user has staff permissions, can call this method. + add_user_with_status_granted(self.user, self.user) + # now make self.user non-staff + self._set_user_non_staff() + self._assert_can_create() + + def test_course_creator_group_denied(self): + # Test index page content with ENABLE_CREATOR_GROUP True, non-staff member with access denied. + with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): + self._set_user_non_staff() + self._set_user_denied() + resp = self._assert_cannot_create() + self.assertFalse(self.request_access_url in resp.content) + self.assertTrue('has-status is-denied' in resp.content) + + def _assert_can_create(self): + """ + Helper method that posts to the index page and checks that the user can create a course. + + Returns the response from the post. + """ + resp = self.client.post(self.index_url) + self.assertTrue('new-course-button' in resp.content) + self.assertFalse(self.request_access_url in resp.content) + self.assertFalse('Email staff to create course' in resp.content) + return resp + + def _assert_cannot_create(self): + """ + Helper method that posts to the index page and checks that the user cannot create a course. + + Returns the response from the post. + """ + resp = self.client.post(self.index_url) + self.assertFalse('new-course-button' in resp.content) + return resp + + def _set_user_non_staff(self): + """ + Sets user as non-staff. + """ + self.user.is_staff = False + self.user.save() + + def _set_user_denied(self): + """ + Sets course creator status to denied in admin table. + """ + self.table_entry = CourseCreator(user=self.user) + self.table_entry.save() + + self.admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo') + self.admin.is_staff = True + + self.deny_request = HttpRequest() + self.deny_request.user = self.admin + + self.creator_admin = CourseCreatorAdmin(self.table_entry, AdminSite()) + + self.table_entry.state = CourseCreator.DENIED + self.creator_admin.save_model(self.deny_request, self.table_entry, None, True) + diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py index b2a97d4f01..f8e341f2cd 100644 --- a/cms/djangoapps/contentstore/views/user.py +++ b/cms/djangoapps/contentstore/views/user.py @@ -35,21 +35,6 @@ def index(request): and course.location.name != '') courses = filter(course_filter, courses) - if settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False): - course_creator_status = 'granted' if request.user.is_staff else 'disallowed_for_this_site' - elif settings.MITX_FEATURES.get('ENABLE_CREATOR_GROUP', False): - course_creator_status = get_course_creator_status(request.user) - if course_creator_status is None: - # User not grandfathered in as an existing user, has not previously visited the dashboard page. - # Add the user to the course creator admin table with status 'unrequested'. - add_user_with_status_unrequested(request.user) - course_creator_status = get_course_creator_status(request.user) - else: - course_creator_status = 'granted' - - request_course_creator_url = reverse('request_course_creator') - csrf_token = csrf(request)['csrf_token'] - return render_to_response('index.html', { 'new_course_template': Location('i4x', 'edx', 'templates', 'course', 'Empty'), 'courses': [(course.display_name, @@ -57,9 +42,9 @@ def index(request): get_lms_link_for_item(course.location, course_id=course.location.course_id)) for course in courses], 'user': request.user, - 'request_course_creator_url': request_course_creator_url, - 'course_creator_status': course_creator_status, - 'csrf': csrf_token + 'request_course_creator_url': reverse('request_course_creator'), + 'course_creator_status': _get_course_creator_status(request.user), + 'csrf': csrf(request)['csrf_token'] }) @@ -67,11 +52,13 @@ def index(request): @ensure_csrf_cookie @login_required def request_course_creator(request): + """ + User has requested course creation access. + """ user_requested_access(request.user) return JsonResponse({"Status": "OK"}) - @login_required @ensure_csrf_cookie def manage_users(request, location): @@ -169,3 +156,28 @@ def remove_user(request, location): remove_user_from_course_group(request.user, user, location, STAFF_ROLE_NAME) return JsonResponse({"Status": "OK"}) + + +def _get_course_creator_status(user): + """ + Helper method for returning the course creator status for a particular user, + taking into account the values of DISABLE_COURSE_CREATION and ENABLE_CREATOR_GROUP. + + If the user passed in has not previously visited the index page, it will be + added with status 'unrequested' if the course creator group is in use. + """ + if user.is_staff: + course_creator_status = 'granted' + elif settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False): + course_creator_status = 'disallowed_for_this_site' + elif settings.MITX_FEATURES.get('ENABLE_CREATOR_GROUP', False): + course_creator_status = get_course_creator_status(user) + if course_creator_status is None: + # User not grandfathered in as an existing user, has not previously visited the dashboard page. + # Add the user to the course creator admin table with status 'unrequested'. + add_user_with_status_unrequested(user) + course_creator_status = get_course_creator_status(user) + else: + course_creator_status = 'granted' + + return course_creator_status From 08a1055cbabdb7a3e0fa6b705bce1205e5bd5da8 Mon Sep 17 00:00:00 2001 From: cahrens Date: Fri, 12 Jul 2013 13:35:27 -0400 Subject: [PATCH 26/71] Updates to index page text. --- cms/djangoapps/contentstore/features/signup.feature | 2 +- cms/djangoapps/contentstore/features/signup.py | 3 +-- cms/templates/index.html | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cms/djangoapps/contentstore/features/signup.feature b/cms/djangoapps/contentstore/features/signup.feature index 03a1c9524a..9706cfe7ea 100644 --- a/cms/djangoapps/contentstore/features/signup.feature +++ b/cms/djangoapps/contentstore/features/signup.feature @@ -9,4 +9,4 @@ Feature: Sign in And I fill in the registration form And I press the Create My Account button on the registration form Then I should see be on the studio home page - And I should see the message "please click on the activation link in your email." + And I should see the message "complete your sign up we need you to verify your email address" diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py index 398f8d074d..363cb4e316 100644 --- a/cms/djangoapps/contentstore/features/signup.py +++ b/cms/djangoapps/contentstore/features/signup.py @@ -2,7 +2,6 @@ #pylint: disable=W0621 from lettuce import world, step -from common import * @step('I fill in the registration form$') @@ -23,7 +22,7 @@ def i_press_the_button_on_the_registration_form(step): @step('I should see be on the studio home page$') def i_should_see_be_on_the_studio_home_page(step): - assert world.browser.find_by_css('div.inner-wrapper') + step.given('I should see the message "My Courses"') @step(u'I should see the message "([^"]*)"$') diff --git a/cms/templates/index.html b/cms/templates/index.html index dd36f19b44..7cd660c088 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -276,7 +276,7 @@

${_('We need to verify your email address')}

-

${_('Almost there! In order to complete your sign up we need you verify your email address (%(email)s). An activation message and next steps should be waiting for you there.') % dict(email=user.email)}

+

${_('Almost there! In order to complete your sign up we need you to verify your email address (%(email)s). An activation message and next steps should be waiting for you there.') % dict(email=user.email)}

From c75041e168f3db047f0ee32e2245e31196c0b28e Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 12 Jul 2013 14:24:22 -0400 Subject: [PATCH 27/71] Studio: adds in disabled/submitting state and logic for course creation button --- cms/static/sass/views/_dashboard.scss | 29 ++++++++++++++++- cms/templates/index.html | 46 ++++++++++++++++----------- common/static/sass/_mixins.scss | 2 +- 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/cms/static/sass/views/_dashboard.scss b/cms/static/sass/views/_dashboard.scss index f64efb184d..a968a2c368 100644 --- a/cms/static/sass/views/_dashboard.scss +++ b/cms/static/sass/views/_dashboard.scss @@ -95,6 +95,7 @@ body.dashboard { @extend .t-action1; @include transform(rotate(45deg)); @include transform-origin(center center); + @include transition(all $tmg-f1 linear 0s); float: right; text-align: right; } @@ -102,7 +103,7 @@ body.dashboard { .ui-toggle-target { @extend .depth1; - @include transition(opacity 0.50s ease-in-out 0s); + @include transition(opacity $tmg-f1 ease-in-out 0s); position: relative; top: -2px; display: none; @@ -156,6 +157,32 @@ body.dashboard { @extend .btn-primary-blue; @extend .t-action3; } + + // specific - request button + .action-request { + position: relative; + overflow: hidden; + + .icon-cog { + @include transition(all $tmg-f1 ease-in-out $tmg-f1); + @include font-size(20); + position: absolute; + top: ($baseline/2); + left: -($baseline); + visibility: hidden; + opacity: 0.0; + } + + &.is-submitting { + padding-left: ($baseline*2); + + .icon-cog { + left: ($baseline*0.75); + visibility: visible; + opacity: 1.0; + } + } + } } .status-update { diff --git a/cms/templates/index.html b/cms/templates/index.html index 7cd660c088..8ef701e3bd 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -8,6 +8,28 @@ +<%block name="jsextra"> + + + <%block name="header_extras"> -<%block name="jsextra"> - - - <%block name="content">
@@ -162,9 +167,14 @@

${_('Your Course Creator Request Status:')}

+ +
+

There was a problem submitting your request

+
+
- +
diff --git a/common/static/sass/_mixins.scss b/common/static/sass/_mixins.scss index 64248734c3..f349ccb963 100644 --- a/common/static/sass/_mixins.scss +++ b/common/static/sass/_mixins.scss @@ -125,7 +125,7 @@ } - &.disabled, &[disabled] { + &.disabled, &[disabled], &.is-disabled { cursor: default; pointer-events: none; opacity: 0.5; From b28b4bfb754f2d4569613edf188a2259a83fdeca Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 12 Jul 2013 14:25:23 -0400 Subject: [PATCH 28/71] Studio: abstracts and revises form/notice UI --- .../sass/elements/_system-feedback.scss | 42 +++++++++++++++++++ cms/static/sass/elements/_system-help.scss | 2 +- cms/static/sass/views/_account.scss | 41 ------------------ 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/cms/static/sass/elements/_system-feedback.scss b/cms/static/sass/elements/_system-feedback.scss index 5022a9f677..90de604aa8 100644 --- a/cms/static/sass/elements/_system-feedback.scss +++ b/cms/static/sass/elements/_system-feedback.scss @@ -1,4 +1,46 @@ // studio - elements - system feedback +// ==================== + +// messages +.message { + @extend .t-copy-sub1; + display: block; +} + +.message-status { + display: none; + @include border-top-radius(2px); + @include box-sizing(border-box); + border-bottom: 2px solid $yellow-d2; + margin: 0 0 $baseline 0; + padding: ($baseline/2) $baseline; + font-weight: 500; + background: $yellow-d1; + color: $white; + + [class^="icon-"] { + position: relative; + top: 1px; + @include font-size(16); + display: inline-block; + margin-right: ($baseline/2); + } + + .text { + display: inline-block; + } + + &.error { + border-color: shade($red, 50%); + background: tint($red, 20%); + } + + &.is-shown { + display: block; + } +} + + // alerts, notifications, prompts, and status communication // ==================== diff --git a/cms/static/sass/elements/_system-help.scss b/cms/static/sass/elements/_system-help.scss index 0f90d9db5c..3b33946e19 100644 --- a/cms/static/sass/elements/_system-help.scss +++ b/cms/static/sass/elements/_system-help.scss @@ -144,7 +144,7 @@ background-color: $gray-l4; .title { - color: $gray-d3; + color: $gray-d2; } .copy { diff --git a/cms/static/sass/views/_account.scss b/cms/static/sass/views/_account.scss index 53f01eee6d..c2cf139400 100644 --- a/cms/static/sass/views/_account.scss +++ b/cms/static/sass/views/_account.scss @@ -252,44 +252,3 @@ body.signup, body.signin { } } } - -// ==================== - -// messages -.message { - @extend .t-copy-sub1; - display: block; -} - -.message-status { - display: none; - @include border-top-radius(2px); - @include box-sizing(border-box); - border-bottom: 2px solid $yellow-d2; - margin: 0 0 $baseline 0; - padding: ($baseline/2) $baseline; - font-weight: 500; - background: $yellow-d1; - color: $white; - - [class^="icon-"] { - position: relative; - top: 1px; - @include font-size(16); - display: inline-block; - margin-right: ($baseline/2); - } - - .text { - display: inline-block; - } - - &.error { - border-color: shade($red, 50%); - background: tint($red, 20%); - } - - &.is-shown { - display: block; - } -} From e1c02b1b31d70da899b98f293c9af12735de94e0 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 12 Jul 2013 16:50:00 -0400 Subject: [PATCH 29/71] Studio: removing preventDefault from course creator request button --- cms/templates/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/cms/templates/index.html b/cms/templates/index.html index 8ef701e3bd..3f55e29db9 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -23,7 +23,6 @@ }); $('#request-coursecreator-submit').click(function(e){ - (e).preventDefault(); $(this).toggleClass('is-disabled is-submitting').find('.label').text('Submitting Your Request'); }); }); From 66eb47fb0d367f5accb4a2a0d9b5696350c345f9 Mon Sep 17 00:00:00 2001 From: ihoover Date: Tue, 16 Jul 2013 11:54:50 -0400 Subject: [PATCH 30/71] Disabled csrf and made auto_login page toggleable with setting --- lms/djangoapps/branding/views.py | 18 ++++++++++++++++++ lms/envs/common.py | 15 +++++++++++++-- lms/urls.py | 6 ++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index dd57e8d4d4..c070c2ade9 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -49,3 +49,21 @@ def courses(request): return courseware.views.courses(request) return courseware.views.university_profile(request, university) + + +def auto_auth(request): + """ + Automatically logs the anonymous user in with a generated random credentials + This view is only accessible when settings.AUTOMATIC_AUTH_FOR_LOAD_TESTING is + true. + """ + + # log the user in + student.views.create_account(request) + + # activate account + request.user.is_active = True + request.user.save() + + # redirect to home-page + return redirect('root') diff --git a/lms/envs/common.py b/lms/envs/common.py index 8b2a1f28cf..ab1b73a88f 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -37,9 +37,14 @@ PLATFORM_NAME = "edX" COURSEWARE_ENABLED = True ENABLE_JASMINE = False +AUTOMATIC_AUTH_FOR_LOAD_TESTING = True + GENERATE_RANDOM_USER_CREDENTIALS = False PERFSTATS = False +# automatic_auth should turn on random_cred of it needs to +GENERATE_RANDOM_USER_CREDENTIALS = GENERATE_RANDOM_USER_CREDENTIALS or AUTOMATIC_AUTH_FOR_LOAD_TESTING + DISCUSSION_SETTINGS = { 'MAX_COMMENT_DEPTH': 2, } @@ -214,7 +219,6 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.messages.context_processors.messages', #'django.core.context_processors.i18n', 'django.contrib.auth.context_processors.auth', # this is required for admin - 'django.core.context_processors.csrf', # necessary for csrf protection # Added for django-wiki 'django.core.context_processors.media', @@ -227,6 +231,10 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'mitxmako.shortcuts.marketing_link_context_processor', ) +# add csrf support unless disabled for load testing +if not AUTOMATIC_AUTH_FOR_LOAD_TESTING: + TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.csrf',) # necessary for csrf protection + STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB MAX_FILEUPLOADS_PER_INPUT = 20 @@ -463,7 +471,6 @@ MIDDLEWARE_CLASSES = ( 'django_comment_client.middleware.AjaxExceptionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', # Instead of AuthenticationMiddleware, we use a cached backed version #'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -482,6 +489,10 @@ MIDDLEWARE_CLASSES = ( 'codejail.django_integration.ConfigureCodeJailMiddleware', ) +# add in csrf middleware unless disabled for load testing +if not AUTOMATIC_AUTH_FOR_LOAD_TESTING: + MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ('django.middleware.csrf.CsrfViewMiddleware',) + ############################### Pipeline ####################################### STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage' diff --git a/lms/urls.py b/lms/urls.py index 085a35b9f4..899586562e 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -439,6 +439,12 @@ urlpatterns = patterns(*urlpatterns) if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +# enable automatic login +if settings.AUTOMATIC_AUTH_FOR_LOAD_TESTING: + urlpatterns += ( + url(r'^auto_auth$', 'branding.views.auto_auth'), + ) + #Custom error pages handler404 = 'static_template_view.views.render_404' handler500 = 'static_template_view.views.render_500' From 9eb1cce352ba6aea30451ccf3d00ba66318757ed Mon Sep 17 00:00:00 2001 From: ihoover Date: Tue, 16 Jul 2013 14:05:41 -0400 Subject: [PATCH 31/71] maximum number of random users --- common/djangoapps/student/views.py | 25 +++++++++++++++++++++++++ lms/djangoapps/branding/views.py | 28 +++++++++++++++++++++++----- lms/envs/common.py | 5 +++-- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 398a3f6efc..129d9d4a19 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -918,6 +918,21 @@ def get_random_post_override(): 'honor_code': u'true', 'terms_of_service': u'true', } +def get_planned_post_override(username, password): + """ + Return a dictionary suitable for passing to post_vars of _do_create_account or post_override + of create_account, with specified username and password. + """ + def id_generator(size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + + return {'username': username, + 'email': id_generator(size=10, chars=string.ascii_lowercase) + "_dummy_test@mitx.mit.edu", + 'password': password, + 'name': (id_generator(size=5, chars=string.ascii_lowercase) + " " + + id_generator(size=7, chars=string.ascii_lowercase)), + 'honor_code': u'true', + 'terms_of_service': u'true', } def create_random_account(create_account_function): def inner_create_random_account(request): @@ -929,6 +944,16 @@ def create_random_account(create_account_function): if settings.GENERATE_RANDOM_USER_CREDENTIALS: create_account = create_random_account(create_account) +def create_random_account_with_name_and_password(create_account_function): + def inner_create_random_account(request, username, password): + return create_account_function(request, post_override=get_planned_post_override(username, password)) + + return inner_create_random_account + +# for load testing we want to create lots of accounts +# with controlled username and password +if settings.AUTOMATIC_AUTH_FOR_LOAD_TESTING: + create_account = create_random_account_with_name_and_password(create_account) @ensure_csrf_cookie def activate_account(request, key): diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index c070c2ade9..f30eec1554 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -58,12 +58,30 @@ def auto_auth(request): true. """ - # log the user in - student.views.create_account(request) + from django.contrib.auth.models import User + from django.contrib.auth import login, authenticate + from random import randint - # activate account - request.user.is_active = True - request.user.save() + # generate random user ceredentials from a small name space + name_base = 'USER_' + pass_base = 'PASS_' + + number = randint(1, settings.MAX_AUTO_AUTH_USERS) + + username = name_base + str(number) + password = pass_base + str(number) + + # if they already are a user, log in + try: + user = User.objects.get(username=username) + user = authenticate(username=username, password=password) + login(request, user) + + except: + # create and activate account info + student.views.create_account(request, username, password) + request.user.is_active = True + request.user.save() # redirect to home-page return redirect('root') diff --git a/lms/envs/common.py b/lms/envs/common.py index ab1b73a88f..57604ba1ff 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -38,12 +38,13 @@ COURSEWARE_ENABLED = True ENABLE_JASMINE = False AUTOMATIC_AUTH_FOR_LOAD_TESTING = True +MAX_AUTO_AUTH_USERS = 2 GENERATE_RANDOM_USER_CREDENTIALS = False PERFSTATS = False -# automatic_auth should turn on random_cred of it needs to -GENERATE_RANDOM_USER_CREDENTIALS = GENERATE_RANDOM_USER_CREDENTIALS or AUTOMATIC_AUTH_FOR_LOAD_TESTING +# # automatic_auth should turn on random_cred of it needs to +# GENERATE_RANDOM_USER_CREDENTIALS = GENERATE_RANDOM_USER_CREDENTIALS or AUTOMATIC_AUTH_FOR_LOAD_TESTING DISCUSSION_SETTINGS = { 'MAX_COMMENT_DEPTH': 2, From e5f44165f85ead53400ebd49f0b942258eaf2e92 Mon Sep 17 00:00:00 2001 From: ihoover Date: Tue, 16 Jul 2013 15:29:38 -0400 Subject: [PATCH 32/71] Made activation flag and ENV_TOKEN --- common/djangoapps/student/views.py | 2 +- lms/djangoapps/branding/views.py | 8 ++++---- lms/envs/aws.py | 2 ++ lms/envs/common.py | 11 +++++++---- lms/urls.py | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 129d9d4a19..01ee90cde8 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -952,7 +952,7 @@ def create_random_account_with_name_and_password(create_account_function): # for load testing we want to create lots of accounts # with controlled username and password -if settings.AUTOMATIC_AUTH_FOR_LOAD_TESTING: +if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): create_account = create_random_account_with_name_and_password(create_account) @ensure_csrf_cookie diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index f30eec1554..2abf50022e 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -53,7 +53,7 @@ def courses(request): def auto_auth(request): """ - Automatically logs the anonymous user in with a generated random credentials + Automatically logs the user in with a generated random credentials This view is only accessible when settings.AUTOMATIC_AUTH_FOR_LOAD_TESTING is true. """ @@ -62,7 +62,7 @@ def auto_auth(request): from django.contrib.auth import login, authenticate from random import randint - # generate random user ceredentials from a small name space + # generate random user ceredentials from a small name space (determined by settings) name_base = 'USER_' pass_base = 'PASS_' @@ -71,14 +71,14 @@ def auto_auth(request): username = name_base + str(number) password = pass_base + str(number) - # if they already are a user, log in + # if they already are a user, log in try: user = User.objects.get(username=username) user = authenticate(username=username, password=password) login(request, user) + # else create and activate account info except: - # create and activate account info student.views.create_account(request, username, password) request.user.is_active = True request.user.save() diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 79178e27a3..ae9dc86687 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -178,6 +178,8 @@ for name, value in ENV_TOKENS.get("CODE_JAIL", {}).items(): COURSES_WITH_UNSAFE_CODE = ENV_TOKENS.get("COURSES_WITH_UNSAFE_CODE", []) +# automatic log in for load testing +MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = ENV_TOKENS.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING') ############################## SECURE AUTH ITEMS ############### # Secret things: passwords, access keys, etc. diff --git a/lms/envs/common.py b/lms/envs/common.py index 57604ba1ff..f8f7aec156 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -37,8 +37,8 @@ PLATFORM_NAME = "edX" COURSEWARE_ENABLED = True ENABLE_JASMINE = False -AUTOMATIC_AUTH_FOR_LOAD_TESTING = True -MAX_AUTO_AUTH_USERS = 2 +# only relevant if MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = True +MAX_AUTO_AUTH_USERS = 20 GENERATE_RANDOM_USER_CREDENTIALS = False PERFSTATS = False @@ -150,6 +150,9 @@ MITX_FEATURES = { # Allow use of the hint managment instructor view. 'ENABLE_HINTER_INSTRUCTOR_VIEW': False, + + # for load testing + 'AUTOMATIC_AUTH_FOR_LOAD_TESTING': False, } # Used for A/B testing @@ -233,7 +236,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ) # add csrf support unless disabled for load testing -if not AUTOMATIC_AUTH_FOR_LOAD_TESTING: +if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.csrf',) # necessary for csrf protection STUDENT_FILEUPLOAD_MAX_SIZE = 4 * 1000 * 1000 # 4 MB @@ -491,7 +494,7 @@ MIDDLEWARE_CLASSES = ( ) # add in csrf middleware unless disabled for load testing -if not AUTOMATIC_AUTH_FOR_LOAD_TESTING: +if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ('django.middleware.csrf.CsrfViewMiddleware',) ############################### Pipeline ####################################### diff --git a/lms/urls.py b/lms/urls.py index 899586562e..1c77530e6b 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -440,7 +440,7 @@ if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # enable automatic login -if settings.AUTOMATIC_AUTH_FOR_LOAD_TESTING: +if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): urlpatterns += ( url(r'^auto_auth$', 'branding.views.auto_auth'), ) From ceacc3b4b5a8607022d346a5a0ac8093918d8997 Mon Sep 17 00:00:00 2001 From: ihoover Date: Tue, 16 Jul 2013 17:01:57 -0400 Subject: [PATCH 33/71] moved auto_auth view to students.view and fixed flag conflicts --- common/djangoapps/student/views.py | 111 +++++++++++++++++++---------- lms/djangoapps/branding/views.py | 36 ---------- lms/envs/common.py | 2 +- lms/urls.py | 2 +- 4 files changed, 76 insertions(+), 75 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 01ee90cde8..ba00c276ce 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -19,6 +19,7 @@ from django.core.context_processors import csrf from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.core.validators import validate_email, validate_slug, ValidationError +from django.core.exceptions import ObjectDoesNotExist from django.db import IntegrityError, transaction from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, Http404 from django.shortcuts import redirect @@ -674,18 +675,20 @@ def create_account(request, post_override=None): subject = ''.join(subject.splitlines()) message = render_to_string('emails/activation_email.txt', d) - try: - if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'): - dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] - message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) + - '-' * 80 + '\n\n' + message) - send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False) - elif not settings.GENERATE_RANDOM_USER_CREDENTIALS: - res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) - except: - log.warning('Unable to send activation email to user', exc_info=True) - js['value'] = _('Could not send activation e-mail.') - return HttpResponse(json.dumps(js)) + # dont send email if we are doing load testing or random user generation for some reason + if not (settings.GENERATE_RANDOM_USER_CREDENTIALS or settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING')): + try: + if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'): + dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] + message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) + + '-' * 80 + '\n\n' + message) + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False) + else: + res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + except: + log.warning('Unable to send activation email to user', exc_info=True) + js['value'] = _('Could not send activation e-mail.') + return HttpResponse(json.dumps(js)) # Immediately after a user creates an account, we log them in. They are only # logged in until they close the browser. They can't log in again until they click @@ -918,25 +921,19 @@ def get_random_post_override(): 'honor_code': u'true', 'terms_of_service': u'true', } -def get_planned_post_override(username, password): - """ - Return a dictionary suitable for passing to post_vars of _do_create_account or post_override - of create_account, with specified username and password. - """ - def id_generator(size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - - return {'username': username, - 'email': id_generator(size=10, chars=string.ascii_lowercase) + "_dummy_test@mitx.mit.edu", - 'password': password, - 'name': (id_generator(size=5, chars=string.ascii_lowercase) + " " + - id_generator(size=7, chars=string.ascii_lowercase)), - 'honor_code': u'true', - 'terms_of_service': u'true', } def create_random_account(create_account_function): - def inner_create_random_account(request): - return create_account_function(request, post_override=get_random_post_override()) + def inner_create_random_account(request, post_override=None): + + # This logic ensures that even if we have both + # GENERATE_RANDOM_USER_CREDENTIALS and + # settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] + # set to true, then create_account will behave they way + # each feature for automated account generation expects + if post_override is None: + post_override = get_random_post_override() + + return create_account_function(request, post_override) return inner_create_random_account @@ -944,16 +941,56 @@ def create_random_account(create_account_function): if settings.GENERATE_RANDOM_USER_CREDENTIALS: create_account = create_random_account(create_account) -def create_random_account_with_name_and_password(create_account_function): - def inner_create_random_account(request, username, password): - return create_account_function(request, post_override=get_planned_post_override(username, password)) - return inner_create_random_account +def auto_auth(request): + """ + Automatically logs the user in with a generated random credentials + This view is only accessible when + settings.MITX_SETTINGS['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] is true. + """ + + def get_dummy_post_data(username, password): + """ + Return a dictionary suitable for passing to post_vars of _do_create_account or post_override + of create_account, with specified username and password. + """ + + return {'username': username, + 'email': username + "_dummy_test@mitx.mit.edu", + 'password': password, + 'name': username + " " + username, + 'honor_code': u'true', + 'terms_of_service': u'true', } + + # generate random user ceredentials from a small name space (determined by settings) + name_base = 'USER_' + pass_base = 'PASS_' + + max_users = 200 + if hasattr(settings, 'MAX_AUTO_AUTH_USERS'): + max_users = settings.MAX_AUTO_AUTH_USERS + + number = random.randint(1, max_users) + + username = name_base + str(number) + password = pass_base + str(number) + + # if they already are a user, log in + try: + user = User.objects.get(username=username) + user = authenticate(username=username, password=password) + login(request, user) + + # else create and activate account info + except ObjectDoesNotExist: + post_override = get_dummy_post_data(username, password) + create_account(request, post_override=post_override) + request.user.is_active = True + request.user.save() + + # return empty success + return HttpResponse('') -# for load testing we want to create lots of accounts -# with controlled username and password -if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): - create_account = create_random_account_with_name_and_password(create_account) @ensure_csrf_cookie def activate_account(request, key): diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index 2abf50022e..dd57e8d4d4 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -49,39 +49,3 @@ def courses(request): return courseware.views.courses(request) return courseware.views.university_profile(request, university) - - -def auto_auth(request): - """ - Automatically logs the user in with a generated random credentials - This view is only accessible when settings.AUTOMATIC_AUTH_FOR_LOAD_TESTING is - true. - """ - - from django.contrib.auth.models import User - from django.contrib.auth import login, authenticate - from random import randint - - # generate random user ceredentials from a small name space (determined by settings) - name_base = 'USER_' - pass_base = 'PASS_' - - number = randint(1, settings.MAX_AUTO_AUTH_USERS) - - username = name_base + str(number) - password = pass_base + str(number) - - # if they already are a user, log in - try: - user = User.objects.get(username=username) - user = authenticate(username=username, password=password) - login(request, user) - - # else create and activate account info - except: - student.views.create_account(request, username, password) - request.user.is_active = True - request.user.save() - - # redirect to home-page - return redirect('root') diff --git a/lms/envs/common.py b/lms/envs/common.py index f8f7aec156..2782a1f074 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -38,7 +38,7 @@ COURSEWARE_ENABLED = True ENABLE_JASMINE = False # only relevant if MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = True -MAX_AUTO_AUTH_USERS = 20 +MAX_AUTO_AUTH_USERS = 200 GENERATE_RANDOM_USER_CREDENTIALS = False PERFSTATS = False diff --git a/lms/urls.py b/lms/urls.py index 1c77530e6b..c43c85aaa1 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -442,7 +442,7 @@ if settings.DEBUG: # enable automatic login if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): urlpatterns += ( - url(r'^auto_auth$', 'branding.views.auto_auth'), + url(r'^auto_auth$', 'student.views.auto_auth'), ) #Custom error pages From ee4bc424cec5171f86850b4df1fce1a56cc26307 Mon Sep 17 00:00:00 2001 From: ihoover Date: Wed, 17 Jul 2013 09:12:07 -0400 Subject: [PATCH 34/71] remove depracated feature flag `GENERATE_RANDOM_USER_CREDENTIALS` --- cms/envs/common.py | 3 --- common/djangoapps/student/views.py | 39 +----------------------------- lms/envs/common.py | 4 --- 3 files changed, 1 insertion(+), 45 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 260aa30cd2..fab3b742f8 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -62,9 +62,6 @@ MITX_FEATURES = { } ENABLE_JASMINE = False -# needed to use lms student app -GENERATE_RANDOM_USER_CREDENTIALS = False - ############################# SET PATH INFORMATION ############################# PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index ba00c276ce..d332e6f738 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -676,7 +676,7 @@ def create_account(request, post_override=None): message = render_to_string('emails/activation_email.txt', d) # dont send email if we are doing load testing or random user generation for some reason - if not (settings.GENERATE_RANDOM_USER_CREDENTIALS or settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING')): + if not (settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING')): try: if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'): dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] @@ -905,43 +905,6 @@ def create_exam_registration(request, post_override=None): return HttpResponse(json.dumps(js), mimetype="application/json") -def get_random_post_override(): - """ - Return a dictionary suitable for passing to post_vars of _do_create_account or post_override - of create_account, with random user info. - """ - def id_generator(size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - - return {'username': "random_" + id_generator(), - 'email': id_generator(size=10, chars=string.ascii_lowercase) + "_dummy_test@mitx.mit.edu", - 'password': id_generator(), - 'name': (id_generator(size=5, chars=string.ascii_lowercase) + " " + - id_generator(size=7, chars=string.ascii_lowercase)), - 'honor_code': u'true', - 'terms_of_service': u'true', } - - -def create_random_account(create_account_function): - def inner_create_random_account(request, post_override=None): - - # This logic ensures that even if we have both - # GENERATE_RANDOM_USER_CREDENTIALS and - # settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] - # set to true, then create_account will behave they way - # each feature for automated account generation expects - if post_override is None: - post_override = get_random_post_override() - - return create_account_function(request, post_override) - - return inner_create_random_account - -# TODO (vshnayder): do we need GENERATE_RANDOM_USER_CREDENTIALS for anything? -if settings.GENERATE_RANDOM_USER_CREDENTIALS: - create_account = create_random_account(create_account) - - def auto_auth(request): """ Automatically logs the user in with a generated random credentials diff --git a/lms/envs/common.py b/lms/envs/common.py index 2782a1f074..46693eb0a5 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -40,12 +40,8 @@ ENABLE_JASMINE = False # only relevant if MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = True MAX_AUTO_AUTH_USERS = 200 -GENERATE_RANDOM_USER_CREDENTIALS = False PERFSTATS = False -# # automatic_auth should turn on random_cred of it needs to -# GENERATE_RANDOM_USER_CREDENTIALS = GENERATE_RANDOM_USER_CREDENTIALS or AUTOMATIC_AUTH_FOR_LOAD_TESTING - DISCUSSION_SETTINGS = { 'MAX_COMMENT_DEPTH': 2, } From b42eacaa8f6d2b3b31a27f803b0eb868c4f653bb Mon Sep 17 00:00:00 2001 From: ihoover Date: Wed, 17 Jul 2013 10:00:24 -0400 Subject: [PATCH 35/71] moved MAX_AUTO_AUTH_USERS to ENV_TOKENS --- common/djangoapps/student/views.py | 5 +---- lms/envs/aws.py | 2 ++ lms/envs/common.py | 3 --- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index d332e6f738..a745e81c19 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -929,10 +929,7 @@ def auto_auth(request): name_base = 'USER_' pass_base = 'PASS_' - max_users = 200 - if hasattr(settings, 'MAX_AUTO_AUTH_USERS'): - max_users = settings.MAX_AUTO_AUTH_USERS - + max_users = settings.MITX_FEATURES.get('MAX_AUTO_AUTH_USERS', 200) number = random.randint(1, max_users) username = name_base + str(number) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index ae9dc86687..ffca2ed25e 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -180,6 +180,8 @@ COURSES_WITH_UNSAFE_CODE = ENV_TOKENS.get("COURSES_WITH_UNSAFE_CODE", []) # automatic log in for load testing MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = ENV_TOKENS.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING') +MITX_FEATURES['MAX_AUTO_AUTH_USERS'] = ENV_TOKENS.get('MAX_AUTO_AUTH_USERS') + ############################## SECURE AUTH ITEMS ############### # Secret things: passwords, access keys, etc. diff --git a/lms/envs/common.py b/lms/envs/common.py index 46693eb0a5..1e274a9c1f 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -37,9 +37,6 @@ PLATFORM_NAME = "edX" COURSEWARE_ENABLED = True ENABLE_JASMINE = False -# only relevant if MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = True -MAX_AUTO_AUTH_USERS = 200 - PERFSTATS = False DISCUSSION_SETTINGS = { From 967ef6ff9479943f581938f3c59cd60b7a986124 Mon Sep 17 00:00:00 2001 From: ihoover Date: Sat, 20 Jul 2013 13:01:37 -0400 Subject: [PATCH 36/71] tests added --- .../student/tests/test_auto_auth.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 common/djangoapps/student/tests/test_auto_auth.py diff --git a/common/djangoapps/student/tests/test_auto_auth.py b/common/djangoapps/student/tests/test_auto_auth.py new file mode 100644 index 0000000000..3f642fd595 --- /dev/null +++ b/common/djangoapps/student/tests/test_auto_auth.py @@ -0,0 +1,113 @@ +from django.test import TestCase +from util.testing import UrlResetMixin +from django.contrib.auth.models import User +from django.conf import settings +from mock import patch +# from copy import deepcopy, copy + +# NEW_SETTINGS = deepcopy(settings) + +# update just the auth flag +# NEW_SETTINGS.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = True +# NEW_SETTINGS.MITX_FEATURES['MAX_AUTO_AUTH_USERS'] = 1 + + +class TestAutoAuthEnabled(UrlResetMixin, TestCase): + """ + Tests for the Auto auth view that we have for load testing. + """ + + @patch.dict("django.conf.settings.MITX_FEATURES", {"AUTOMATIC_AUTH_FOR_LOAD_TESTING": True}) + def setUp(self): + # Patching the settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] + # value affects the contents of urls.py, + # so we need to call super.setUp() which reloads urls.py (because + # of the UrlResetMixin) + super(TestAutoAuthEnabled, self).setUp() + + def test_create_user(self): + """ + tests that user gets created when visiting the page + """ + print settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] + url = '/auto_auth' + self.client.get(url) + + qset = User.objects.all() + + # assert user was created and is active + self.assertEqual(qset.count(), 1) + user = qset[0] + assert user.is_active + + @patch.dict("django.conf.settings.MITX_FEATURES", {"MAX_AUTO_AUTH_USERS": 10000000}) + def test_create_multiple_users(self): + """ + speculative test to make sure multiple users are created. + Technically, this test is probabalistic. + + However, my judgement is that if the chance of failing due + only to bad luck is less than 1:10^1000, we are OK (it is more + likely that the test failed because the jenkins server was hit + by an asteroid, or the person running the tests was a freind + of Hamlet's). + """ + + url = '/auto_auth' + + # hit the url a few times + # mathematically, is much more efficient + # to hit the site many many times, and + # have a smaller MAX user count, but it is + # the GET request that actually takes a lot + # of time. + for i in range(200): + self.client.get(url) + + qset = User.objects.all() + + # make sure it is the smae user + self.assertGreater(qset.count(), 1) + + @patch.dict("django.conf.settings.MITX_FEATURES", {"MAX_AUTO_AUTH_USERS": 1}) + def test_login(self): + """ + test that when we have reached the limit for automatic users + a subsequent request results in an already existant one being + logged in. + """ + + # auto-generate 1 user (the max) + url = '/auto_auth' + self.client.get(url) + + # go to the site again + self.client.get(url) + qset = User.objects.all() + + # make sure it is the smae user + self.assertEqual(qset.count(), 1) + + +class TestAutoAuthDisabled(UrlResetMixin, TestCase): + """ + Test that the page is inaccessible with default settings + """ + + @patch.dict("django.conf.settings.MITX_FEATURES", {"AUTOMATIC_AUTH_FOR_LOAD_TESTING": False}) + def setUp(self): + # Patching the settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] + # value affects the contents of urls.py, + # so we need to call super.setUp() which reloads urls.py (because + # of the UrlResetMixin) + super(TestAutoAuthDisabled, self).setUp() + + def test_404(self): + """ + make sure automatic authentication is invisible + """ + + url = '/auto_auth' + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) From d7de09442e6087f6241a4195ebb55ff4c84e1de2 Mon Sep 17 00:00:00 2001 From: ihoover Date: Sat, 20 Jul 2013 16:14:41 -0400 Subject: [PATCH 37/71] tweaks --- common/djangoapps/student/tests/test_auto_auth.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/common/djangoapps/student/tests/test_auto_auth.py b/common/djangoapps/student/tests/test_auto_auth.py index 3f642fd595..33eee06947 100644 --- a/common/djangoapps/student/tests/test_auto_auth.py +++ b/common/djangoapps/student/tests/test_auto_auth.py @@ -1,15 +1,7 @@ from django.test import TestCase from util.testing import UrlResetMixin from django.contrib.auth.models import User -from django.conf import settings from mock import patch -# from copy import deepcopy, copy - -# NEW_SETTINGS = deepcopy(settings) - -# update just the auth flag -# NEW_SETTINGS.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = True -# NEW_SETTINGS.MITX_FEATURES['MAX_AUTO_AUTH_USERS'] = 1 class TestAutoAuthEnabled(UrlResetMixin, TestCase): @@ -29,7 +21,7 @@ class TestAutoAuthEnabled(UrlResetMixin, TestCase): """ tests that user gets created when visiting the page """ - print settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] + url = '/auto_auth' self.client.get(url) From 18352e9f2043cd145f12b910bd47c2a095afd4d1 Mon Sep 17 00:00:00 2001 From: ihoover Date: Sat, 20 Jul 2013 16:20:32 -0400 Subject: [PATCH 38/71] comment tweaks --- common/djangoapps/student/tests/test_auto_auth.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/common/djangoapps/student/tests/test_auto_auth.py b/common/djangoapps/student/tests/test_auto_auth.py index 33eee06947..e98fb1071a 100644 --- a/common/djangoapps/student/tests/test_auto_auth.py +++ b/common/djangoapps/student/tests/test_auto_auth.py @@ -47,12 +47,9 @@ class TestAutoAuthEnabled(UrlResetMixin, TestCase): url = '/auto_auth' - # hit the url a few times - # mathematically, is much more efficient - # to hit the site many many times, and - # have a smaller MAX user count, but it is - # the GET request that actually takes a lot - # of time. + # hit the url a few times. Mathematically, is much more efficient + # to hit the site many many times, and have a smaller MAX user + # count, but it is the GET request that actually takes a lot of time. for i in range(200): self.client.get(url) From 5168a080ffe050866ecf04fc065fb6c98cd002c0 Mon Sep 17 00:00:00 2001 From: cahrens Date: Mon, 22 Jul 2013 14:45:22 -0400 Subject: [PATCH 39/71] Don't add users marked with is_staff to course creation table. --- .../contentstore/tests/test_users.py | 16 ++++--------- .../course_creators/tests/test_views.py | 10 ++++++++ cms/djangoapps/course_creators/views.py | 23 +++++++++++++++---- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_users.py b/cms/djangoapps/contentstore/tests/test_users.py index bba4d42f4d..701dd246d8 100644 --- a/cms/djangoapps/contentstore/tests/test_users.py +++ b/cms/djangoapps/contentstore/tests/test_users.py @@ -47,6 +47,9 @@ class IndexCourseCreatorTests(CourseTestCase): self.enable_creator_group = {"ENABLE_CREATOR_GROUP": True} + self.admin = User.objects.create_user('Mark', 'mark+courses@edx.org', 'foo') + self.admin.is_staff = True + def test_get_course_creator_status_disable_creation(self): # DISABLE_COURSE_CREATION is True (this is the case on edx, where we have a marketing site). # Only edx staff can create courses. @@ -80,17 +83,14 @@ class IndexCourseCreatorTests(CourseTestCase): # ENABLE_CREATOR_GROUP is True. This is the case on edge. # Check return value for a non-staff user who has been granted access. with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): - # self.user has staff permissions, can call this method. - add_user_with_status_granted(self.user, self.user) - # now make self.user non-staff self._set_user_non_staff() + add_user_with_status_granted(self.admin, self.user) self.assertEquals('granted', _get_course_creator_status(self.user)) def test_get_course_creator_status_creator_group_denied(self): # ENABLE_CREATOR_GROUP is True. This is the case on edge. # Check return value for a non-staff user who has been denied access. with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): - # make self.user non-staff self._set_user_non_staff() self._set_user_denied() self.assertEquals('denied', _get_course_creator_status(self.user)) @@ -137,10 +137,8 @@ class IndexCourseCreatorTests(CourseTestCase): def test_course_creator_group_granted(self): # Test index page content with ENABLE_CREATOR_GROUP True, non-staff member with access granted. with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group): - # self.user has staff permissions, can call this method. - add_user_with_status_granted(self.user, self.user) - # now make self.user non-staff self._set_user_non_staff() + add_user_with_status_granted(self.admin, self.user) self._assert_can_create() def test_course_creator_group_denied(self): @@ -188,9 +186,6 @@ class IndexCourseCreatorTests(CourseTestCase): self.table_entry = CourseCreator(user=self.user) self.table_entry.save() - self.admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo') - self.admin.is_staff = True - self.deny_request = HttpRequest() self.deny_request.user = self.admin @@ -198,4 +193,3 @@ class IndexCourseCreatorTests(CourseTestCase): self.table_entry.state = CourseCreator.DENIED self.creator_admin.save_model(self.deny_request, self.table_entry, None, True) - diff --git a/cms/djangoapps/course_creators/tests/test_views.py b/cms/djangoapps/course_creators/tests/test_views.py index 943120f3c0..95c50ffb76 100644 --- a/cms/djangoapps/course_creators/tests/test_views.py +++ b/cms/djangoapps/course_creators/tests/test_views.py @@ -80,3 +80,13 @@ class CourseCreatorView(TestCase): # user from the authz course creator group (and that can only be done by an admin). user_requested_access(self.user) self.assertEqual('granted', get_course_creator_status(self.user)) + + def test_add_user_unrequested_staff(self): + # Users marked as is_staff will not be added to the course creator table. + add_user_with_status_unrequested(self.admin) + self.assertIsNone(get_course_creator_status(self.admin)) + + def test_add_user_granted_staff(self): + # Users marked as is_staff will not be added to the course creator table. + add_user_with_status_granted(self.admin, self.admin) + self.assertIsNone(get_course_creator_status(self.admin)) diff --git a/cms/djangoapps/course_creators/views.py b/cms/djangoapps/course_creators/views.py index 6b2d91acb2..e9b38ed169 100644 --- a/cms/djangoapps/course_creators/views.py +++ b/cms/djangoapps/course_creators/views.py @@ -12,6 +12,9 @@ def add_user_with_status_unrequested(user): If the user is already in the table, this method is a no-op (state will not be changed). + + If the user is marked as is_staff, this method is a no-op (user + will not be added to table). """ _add_user(user, CourseCreator.UNREQUESTED) @@ -20,14 +23,17 @@ def add_user_with_status_granted(caller, user): """ Adds a user to the course creator table with status 'granted'. - This method also adds the user to the course creator group maintained by authz.py. + If appropriate, this method also adds the user to the course creator group maintained by authz.py. Caller must have staff permissions. If the user is already in the table, this method is a no-op (state will not be changed). + + If the user is marked as is_staff, this method is a no-op (user + will not be added to table, nor added to authz.py group). """ - _add_user(user, CourseCreator.GRANTED) - update_course_creator_group(caller, user, True) + if _add_user(user, CourseCreator.GRANTED): + update_course_creator_group(caller, user, True) def update_course_creator_group(caller, user, add): @@ -78,9 +84,16 @@ def _add_user(user, state): """ Adds a user to the course creator table with the specified state. + Returns True if user was added to table, else False. + If the user is already in the table, this method is a no-op - (state will not be changed). + (state will not be changed, method will return False). + + If the user is marked as is_staff, this method is a no-op (False will be returned). """ - if CourseCreator.objects.filter(user=user).count() == 0: + if not user.is_staff and CourseCreator.objects.filter(user=user).count() == 0: entry = CourseCreator(user=user, state=state) entry.save() + return True + + return False From 0833181eccece0c1e2946c676d6a6ee5ed5818a7 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 22 Jul 2013 16:30:26 -0400 Subject: [PATCH 40/71] Studio: updates older extend references and solves Sass warnings/dupe display of accessibility-based text --- cms/static/sass/_base.scss | 2 +- cms/static/sass/views/_dashboard.scss | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 48a3e25782..e4495d0248 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -448,7 +448,7 @@ p, ul, ol, dl { // actions .list-actions { - @extend .no-list; + @extend .cont-no-list; .action-item { margin-bottom: ($baseline/4); diff --git a/cms/static/sass/views/_dashboard.scss b/cms/static/sass/views/_dashboard.scss index a968a2c368..6772f8addb 100644 --- a/cms/static/sass/views/_dashboard.scss +++ b/cms/static/sass/views/_dashboard.scss @@ -72,7 +72,7 @@ body.dashboard { overflow: hidden; .ui-toggle-control { - @extend .depth2; + @extend .ui-depth2; @extend .btn-secondary-gray; @include clearfix(); display: block; @@ -102,7 +102,7 @@ body.dashboard { } .ui-toggle-target { - @extend .depth1; + @extend .ui-depth1; @include transition(opacity $tmg-f1 ease-in-out 0s); position: relative; top: -2px; @@ -188,7 +188,7 @@ body.dashboard { .status-update { .label { - @extend .text-sr; + @extend .cont-text-sr; } .value { @@ -238,7 +238,7 @@ body.dashboard { &.is-unrequested { .title { - @extend .text-sr; + @extend .cont-text-sr; } } From 3d49a4619765e53a2beece40f4e62da9687a6b8c Mon Sep 17 00:00:00 2001 From: cahrens Date: Tue, 23 Jul 2013 10:16:45 -0400 Subject: [PATCH 41/71] Add error handler. --- cms/templates/index.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cms/templates/index.html b/cms/templates/index.html index f8e6f4605f..30ddcc4c86 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -18,9 +18,15 @@ $(this).closest('.wrapper-creationrights').toggleClass('is-shown').find('.ui-toggle-control').toggleClass('current'); }); - $('#request-coursecreator').ajaxForm(function() { + var reloadPage = function () { location.reload(); - }); + }; + + var showError = function () { + + }; + + $('#request-coursecreator').ajaxForm({error: showError, success: reloadPage}); $('#request-coursecreator-submit').click(function(e){ $(this).toggleClass('is-disabled is-submitting').find('.label').text('Submitting Your Request'); @@ -168,7 +174,7 @@
-

There was a problem submitting your request

+

${_('There was a problem submitting your request')}

From 1da5af53d9a161059da5a1b0146677019f738913 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 23 Jul 2013 10:37:07 -0400 Subject: [PATCH 42/71] Studio: revises styling/message copy for authorship rights request error --- cms/static/sass/views/_dashboard.scss | 15 +++++++++++++++ cms/templates/index.html | 8 ++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/cms/static/sass/views/_dashboard.scss b/cms/static/sass/views/_dashboard.scss index 6772f8addb..63c8ab36fd 100644 --- a/cms/static/sass/views/_dashboard.scss +++ b/cms/static/sass/views/_dashboard.scss @@ -159,6 +159,7 @@ body.dashboard { } // specific - request button + // BT: should we abstract these states out for all buttons like this .action-request { position: relative; overflow: hidden; @@ -173,6 +174,7 @@ body.dashboard { opacity: 0.0; } + // state: submitting &.is-submitting { padding-left: ($baseline*2); @@ -182,6 +184,19 @@ body.dashboard { opacity: 1.0; } } + + // state: has an error + &.has-error { + padding-left: ($baseline*2); + background: $red; + border-color: $red-d1; + + .icon-cog { + left: ($baseline*0.75); + visibility: visible; + opacity: 1.0; + } + } } } diff --git a/cms/templates/index.html b/cms/templates/index.html index 30ddcc4c86..ba24099e63 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -23,7 +23,8 @@ }; var showError = function () { - + $('#request-coursecreator-submit').toggleClass('has-error').find('.label').text('Sorry, there was error with your request'); + $('#request-coursecreator-submit').find('.icon-cog').toggleClass('icon-spin'); }; $('#request-coursecreator').ajaxForm({error: showError, success: reloadPage}); @@ -172,11 +173,6 @@

${_('Your Course Creator Request Status:')}

- -
-

${_('There was a problem submitting your request')}

-
-
From ea31b17d576337f12a2678a3947c045caf7d6eaa Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 23 Jul 2013 16:22:51 -0400 Subject: [PATCH 43/71] Fix up auto auth tests. --- .../student/tests/test_auto_auth.py | 76 +++++++------------ 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/common/djangoapps/student/tests/test_auto_auth.py b/common/djangoapps/student/tests/test_auto_auth.py index 3f642fd595..1ac2460c17 100644 --- a/common/djangoapps/student/tests/test_auto_auth.py +++ b/common/djangoapps/student/tests/test_auto_auth.py @@ -1,18 +1,11 @@ from django.test import TestCase -from util.testing import UrlResetMixin +from django.test.client import Client from django.contrib.auth.models import User -from django.conf import settings +from util.testing import UrlResetMixin from mock import patch -# from copy import deepcopy, copy - -# NEW_SETTINGS = deepcopy(settings) - -# update just the auth flag -# NEW_SETTINGS.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] = True -# NEW_SETTINGS.MITX_FEATURES['MAX_AUTO_AUTH_USERS'] = 1 -class TestAutoAuthEnabled(UrlResetMixin, TestCase): +class AutoAuthEnabledTestCase(UrlResetMixin, TestCase): """ Tests for the Auto auth view that we have for load testing. """ @@ -23,15 +16,15 @@ class TestAutoAuthEnabled(UrlResetMixin, TestCase): # value affects the contents of urls.py, # so we need to call super.setUp() which reloads urls.py (because # of the UrlResetMixin) - super(TestAutoAuthEnabled, self).setUp() + super(AutoAuthEnabledTestCase, self).setUp() + self.url = '/auto_auth' + self.client = Client() def test_create_user(self): """ - tests that user gets created when visiting the page + Test that user gets created when visiting the page. """ - print settings.MITX_FEATURES['AUTOMATIC_AUTH_FOR_LOAD_TESTING'] - url = '/auto_auth' - self.client.get(url) + self.client.get(self.url) qset = User.objects.all() @@ -40,43 +33,29 @@ class TestAutoAuthEnabled(UrlResetMixin, TestCase): user = qset[0] assert user.is_active - @patch.dict("django.conf.settings.MITX_FEATURES", {"MAX_AUTO_AUTH_USERS": 10000000}) - def test_create_multiple_users(self): + @patch('student.views.random.randint') + def test_create_multiple_users(self, randint): """ - speculative test to make sure multiple users are created. - Technically, this test is probabalistic. - - However, my judgement is that if the chance of failing due - only to bad luck is less than 1:10^1000, we are OK (it is more - likely that the test failed because the jenkins server was hit - by an asteroid, or the person running the tests was a freind - of Hamlet's). + Test to make sure multiple users are created. """ + randint.return_value = 1 + self.client.get(self.url) - url = '/auto_auth' - - # hit the url a few times - # mathematically, is much more efficient - # to hit the site many many times, and - # have a smaller MAX user count, but it is - # the GET request that actually takes a lot - # of time. - for i in range(200): - self.client.get(url) + randint.return_value = 2 + self.client.get(self.url) qset = User.objects.all() - # make sure it is the smae user - self.assertGreater(qset.count(), 1) + # make sure that USER_1 and USER_2 were created + self.assertEqual(qset.count(), 2) @patch.dict("django.conf.settings.MITX_FEATURES", {"MAX_AUTO_AUTH_USERS": 1}) - def test_login(self): + def test_login_already_created_user(self): """ - test that when we have reached the limit for automatic users + Test that when we have reached the limit for automatic users a subsequent request results in an already existant one being logged in. """ - # auto-generate 1 user (the max) url = '/auto_auth' self.client.get(url) @@ -85,11 +64,11 @@ class TestAutoAuthEnabled(UrlResetMixin, TestCase): self.client.get(url) qset = User.objects.all() - # make sure it is the smae user + # make sure it is the same user self.assertEqual(qset.count(), 1) -class TestAutoAuthDisabled(UrlResetMixin, TestCase): +class AutoAuthDisabledTestCase(UrlResetMixin): """ Test that the page is inaccessible with default settings """ @@ -100,14 +79,13 @@ class TestAutoAuthDisabled(UrlResetMixin, TestCase): # value affects the contents of urls.py, # so we need to call super.setUp() which reloads urls.py (because # of the UrlResetMixin) - super(TestAutoAuthDisabled, self).setUp() + super(AutoAuthDisabledTestCase, self).setUp() + self.url = '/auto_auth' + self.client = Client() - def test_404(self): + def test_auto_auth_disabled(self): """ - make sure automatic authentication is invisible + Make sure automatic authentication is disabled. """ - - url = '/auto_auth' - response = self.client.get(url) - + response = self.client.get(self.url) self.assertEqual(response.status_code, 404) From c6c3e4b9d31a4b338d13131ac073a6fafda8617c Mon Sep 17 00:00:00 2001 From: ihoover Date: Tue, 23 Jul 2013 16:23:54 -0400 Subject: [PATCH 44/71] working under cms too --- cms/urls.py | 6 ++++++ lms/urls.py | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cms/urls.py b/cms/urls.py index 711742d89f..55b8d87c2a 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -149,6 +149,12 @@ if settings.MITX_FEATURES.get('ENABLE_SERVICE_STATUS'): urlpatterns += (url(r'^admin/', include(admin.site.urls)),) +# enable automatic login +if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): + urlpatterns += ( + url(r'^auto_auth$', 'student.views.auto_auth'), + ) + urlpatterns = patterns(*urlpatterns) # Custom error pages diff --git a/lms/urls.py b/lms/urls.py index c43c85aaa1..35aff1481c 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -434,17 +434,17 @@ if settings.MITX_FEATURES.get('ENABLE_HINTER_INSTRUCTOR_VIEW'): 'instructor.hint_manager.hint_manager', name="hint_manager"), ) -urlpatterns = patterns(*urlpatterns) - -if settings.DEBUG: - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) - # enable automatic login if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): urlpatterns += ( url(r'^auto_auth$', 'student.views.auto_auth'), ) +urlpatterns = patterns(*urlpatterns) + +if settings.DEBUG: + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + #Custom error pages handler404 = 'static_template_view.views.render_404' handler500 = 'static_template_view.views.render_500' From 402ae4d8bd4cf8e315f7b86d3185777e08e2d9f8 Mon Sep 17 00:00:00 2001 From: ihoover Date: Tue, 23 Jul 2013 17:32:21 -0400 Subject: [PATCH 45/71] added csrf tests --- .../student/tests/test_auto_auth.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/student/tests/test_auto_auth.py b/common/djangoapps/student/tests/test_auto_auth.py index 818db024e5..94bdeb5dfd 100644 --- a/common/djangoapps/student/tests/test_auto_auth.py +++ b/common/djangoapps/student/tests/test_auto_auth.py @@ -3,6 +3,7 @@ from django.test.client import Client from django.contrib.auth.models import User from util.testing import UrlResetMixin from mock import patch +from django.core.urlresolvers import reverse class AutoAuthEnabledTestCase(UrlResetMixin, TestCase): @@ -68,8 +69,17 @@ class AutoAuthEnabledTestCase(UrlResetMixin, TestCase): # make sure it is the same user self.assertEqual(qset.count(), 1) + def test_csrf_disabled(self): + """ + test that when load testing, csrf protection is off + """ + self.client = Client(enforce_csrf_checks=True) + csrf_protected_url = reverse("signin_user") + response = self.client.get(csrf_protected_url) + self.assertEqual(response.status_code, 200) -class AutoAuthDisabledTestCase(UrlResetMixin): + +class AutoAuthDisabledTestCase(UrlResetMixin, TestCase): """ Test that the page is inaccessible with default settings """ @@ -90,3 +100,13 @@ class AutoAuthDisabledTestCase(UrlResetMixin): """ response = self.client.get(self.url) self.assertEqual(response.status_code, 404) + + def test_csrf_enabled(self): + """ + test that when not load testing, csrf protection is on + """ + self.client = Client(enforce_csrf_checks=True) + csrf_protected_url = reverse("signin_user") + response = self.client.post(csrf_protected_url) + self.assertEqual(response.status_code, 403) + From 1a0055ae77d3f967f648af0435efe8edae819ab4 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Wed, 24 Jul 2013 16:44:22 -0400 Subject: [PATCH 46/71] Blank common and advanced problems now display an inline score of '0 points' upon creation. --- common/lib/xmodule/xmodule/progress.py | 4 ++-- common/lib/xmodule/xmodule/tests/test_progress.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/progress.py b/common/lib/xmodule/xmodule/progress.py index bad5105fd0..4e3a8bc93e 100644 --- a/common/lib/xmodule/xmodule/progress.py +++ b/common/lib/xmodule/xmodule/progress.py @@ -146,7 +146,7 @@ class Progress(object): sending Progress objects to js to limit dependencies. ''' if progress is None: - return "NA" + return "0" return progress.ternary_str() @staticmethod @@ -157,5 +157,5 @@ class Progress(object): passing Progress objects to js to limit dependencies. ''' if progress is None: - return "NA" + return "0" return str(progress) diff --git a/common/lib/xmodule/xmodule/tests/test_progress.py b/common/lib/xmodule/xmodule/tests/test_progress.py index d1a32d5de5..e7af7a0c09 100644 --- a/common/lib/xmodule/xmodule/tests/test_progress.py +++ b/common/lib/xmodule/xmodule/tests/test_progress.py @@ -90,15 +90,15 @@ class ProgressTest(unittest.TestCase): self.assertEqual(Progress.to_js_status_str(self.not_started), "none") self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress") self.assertEqual(Progress.to_js_status_str(self.done), "done") - self.assertEqual(Progress.to_js_status_str(None), "NA") + self.assertEqual(Progress.to_js_status_str(None), "0") def test_to_js_detail_str(self): '''Test the Progress.to_js_detail_str() method''' f = Progress.to_js_detail_str for p in (self.not_started, self.half_done, self.done): self.assertEqual(f(p), str(p)) - # But None should be encoded as NA - self.assertEqual(f(None), "NA") + # But None should be encoded as 0 + self.assertEqual(f(None), "0") def test_add(self): '''Test the Progress.add_counts() method''' From 56883d65a1e79cb596f89844e392712dab681e4e Mon Sep 17 00:00:00 2001 From: ihoover Date: Tue, 23 Jul 2013 18:58:34 -0400 Subject: [PATCH 47/71] csrf test fix remove test that csrf middleware is disabled (since we can't seem to reload middleware between tests) move definitions from setuo into test method --- cms/envs/common.py | 10 ++++++-- .../student/tests/test_auto_auth.py | 25 +++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index fab3b742f8..0ca0d1b65f 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -105,9 +105,12 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.static', 'django.contrib.messages.context_processors.messages', 'django.contrib.auth.context_processors.auth', # this is required for admin - 'django.core.context_processors.csrf', # necessary for csrf protection ) +# add csrf support unless disabled for load testing +if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): + TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.csrf',) # necessary for csrf protection + LMS_BASE = None #################### CAPA External Code Evaluation ############################# @@ -139,7 +142,6 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', 'method_override.middleware.MethodOverrideMiddleware', # Instead of AuthenticationMiddleware, we use a cache-backed version @@ -155,6 +157,10 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.transaction.TransactionMiddleware' ) +# add in csrf middleware unless disabled for load testing +if not MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_LOAD_TESTING'): + MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ('django.middleware.csrf.CsrfViewMiddleware',) + ############################ SIGNAL HANDLERS ################################ # This is imported to register the exception signal handling that logs exceptions import monitoring.exceptions # noqa diff --git a/common/djangoapps/student/tests/test_auto_auth.py b/common/djangoapps/student/tests/test_auto_auth.py index 94bdeb5dfd..dca2937b01 100644 --- a/common/djangoapps/student/tests/test_auto_auth.py +++ b/common/djangoapps/student/tests/test_auto_auth.py @@ -3,7 +3,7 @@ from django.test.client import Client from django.contrib.auth.models import User from util.testing import UrlResetMixin from mock import patch -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse, NoReverseMatch class AutoAuthEnabledTestCase(UrlResetMixin, TestCase): @@ -19,6 +19,8 @@ class AutoAuthEnabledTestCase(UrlResetMixin, TestCase): # of the UrlResetMixin) super(AutoAuthEnabledTestCase, self).setUp() self.url = '/auto_auth' + self.cms_csrf_url = "signup" + self.lms_csrf_url = "signin_user" self.client = Client() def test_create_user(self): @@ -69,15 +71,6 @@ class AutoAuthEnabledTestCase(UrlResetMixin, TestCase): # make sure it is the same user self.assertEqual(qset.count(), 1) - def test_csrf_disabled(self): - """ - test that when load testing, csrf protection is off - """ - self.client = Client(enforce_csrf_checks=True) - csrf_protected_url = reverse("signin_user") - response = self.client.get(csrf_protected_url) - self.assertEqual(response.status_code, 200) - class AutoAuthDisabledTestCase(UrlResetMixin, TestCase): """ @@ -105,8 +98,14 @@ class AutoAuthDisabledTestCase(UrlResetMixin, TestCase): """ test that when not load testing, csrf protection is on """ + cms_csrf_url = "signup" + lms_csrf_url = "signin_user" self.client = Client(enforce_csrf_checks=True) - csrf_protected_url = reverse("signin_user") - response = self.client.post(csrf_protected_url) - self.assertEqual(response.status_code, 403) + try: + csrf_protected_url = reverse(cms_csrf_url) + response = self.client.post(csrf_protected_url) + except NoReverseMatch: + csrf_protected_url = reverse(lms_csrf_url) + response = self.client.post(csrf_protected_url) + self.assertEqual(response.status_code, 403) From 214a814b946b7c06b7251a5d6953e7168d5e15a8 Mon Sep 17 00:00:00 2001 From: Giulio Gratta Date: Mon, 22 Jul 2013 14:24:09 -0700 Subject: [PATCH 48/71] Adjustment to show sequence tool tips in LMS --- common/lib/xmodule/xmodule/css/sequence/display.scss | 3 +++ lms/templates/seq_module.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/sequence/display.scss b/common/lib/xmodule/xmodule/css/sequence/display.scss index 4baf60a8fe..1e82b31f10 100644 --- a/common/lib/xmodule/xmodule/css/sequence/display.scss +++ b/common/lib/xmodule/xmodule/css/sequence/display.scss @@ -211,6 +211,8 @@ nav.sequence-nav { @include transition(all .1s $ease-in-out-quart 0s); white-space: pre; z-index: 99; + visibility: hidden; + pointer-events: none; &:empty { background: none; @@ -238,6 +240,7 @@ nav.sequence-nav { display: block; margin-top: 4px; opacity: 1.0; + visibility: visible; } } } diff --git a/lms/templates/seq_module.html b/lms/templates/seq_module.html index f372d495ca..fff1279cd6 100644 --- a/lms/templates/seq_module.html +++ b/lms/templates/seq_module.html @@ -3,7 +3,7 @@ - +
    % for idx, item in enumerate(items): @@ -16,7 +16,7 @@ data-id="${item['id']}" data-element="${idx+1}" href="javascript:void(0);"> -

    ${item['title']}, ${item['type']}

    +

    ${item['title']}, ${item['type']}

    % endfor From 1bcafd32a977cb5b5e1aa20dd76bd422fdf00a05 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Thu, 25 Jul 2013 09:34:51 -0400 Subject: [PATCH 49/71] Make the service-variant flag on manage.py longform only --- manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage.py b/manage.py index 948bf147d4..d6b74025f5 100755 --- a/manage.py +++ b/manage.py @@ -34,7 +34,7 @@ def parse_args(): help="Which django settings module to use from inside of lms.envs. If not provided, the DJANGO_SETTINGS_MODULE " "environment variable will be used if it is set, otherwise will default to lms.envs.dev") lms.add_argument( - '-s', '--service-variant', + '--service-variant', choices=['lms', 'lms-xml', 'lms-preview'], default='lms', help='Which service variant to run, when using the aws environment') From 2efcb0cdcafabfba2b5a05d92838fbb9b07a5d68 Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Wed, 24 Jul 2013 10:09:12 -0400 Subject: [PATCH 50/71] Change default names for various LMS components Embedded discussion component defaults to "Discussion" Blank HTML page defaults to "Text" Video component defaults to "Video" These default names show up in tooltips. --- cms/djangoapps/contentstore/features/html-editor.py | 2 +- cms/djangoapps/contentstore/features/video-editor.py | 2 +- common/lib/xmodule/xmodule/discussion_module.py | 12 ++++++++---- common/lib/xmodule/xmodule/html_module.py | 2 +- common/lib/xmodule/xmodule/video_module.py | 8 +++++--- .../courseware/tests/test_module_render.py | 5 ++--- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/cms/djangoapps/contentstore/features/html-editor.py b/cms/djangoapps/contentstore/features/html-editor.py index b03388c89a..c3e0afa480 100644 --- a/cms/djangoapps/contentstore/features/html-editor.py +++ b/cms/djangoapps/contentstore/features/html-editor.py @@ -14,4 +14,4 @@ def i_created_blank_html_page(step): @step('I see only the HTML display name setting$') def i_see_only_the_html_display_name(step): - world.verify_all_setting_entries([['Display Name', "Blank HTML Page", False]]) + world.verify_all_setting_entries([['Display Name', "Text", False]]) diff --git a/cms/djangoapps/contentstore/features/video-editor.py b/cms/djangoapps/contentstore/features/video-editor.py index e0f76b30ad..93d638e621 100644 --- a/cms/djangoapps/contentstore/features/video-editor.py +++ b/cms/djangoapps/contentstore/features/video-editor.py @@ -7,7 +7,7 @@ from lettuce import world, step @step('I see the correct settings and default values$') def i_see_the_correct_settings_and_values(step): world.verify_all_setting_entries([['Default Speed', 'OEoXaMPEzfM', False], - ['Display Name', 'Video Title', False], + ['Display Name', 'Video', False], ['Download Track', '', False], ['Download Video', '', False], ['Show Captions', 'True', False], diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index fac6a498e5..2c6cc9f4dd 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -12,10 +12,14 @@ class DiscussionFields(object): display_name = String( display_name="Display Name", help="Display name for this module", - default="Discussion Tag", - scope=Scope.settings) - data = String(help="XML data for the problem", scope=Scope.content, - default="") + default="Discussion", + scope=Scope.settings + ) + data = String( + help="XML data for the problem", + scope=Scope.content, + default="" + ) discussion_category = String( display_name="Category", default="Week 1", diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index a482a86fe7..1cc7bed948 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -25,7 +25,7 @@ class HtmlFields(object): scope=Scope.settings, # it'd be nice to have a useful default but it screws up other things; so, # use display_name_with_default for those - default="Blank HTML Page" + default="Text" ) data = String(help="Html contents to display for this module", default=u"", scope=Scope.content) source_code = String(help="Source code for LaTeX documents. This feature is not well-supported.", scope=Scope.settings) diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index 1d9ad35135..6c5640346e 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -27,11 +27,13 @@ class VideoFields(object): scope=Scope.settings, # it'd be nice to have a useful default but it screws up other things; so, # use display_name_with_default for those - default="Video Title" + default="Video" ) - data = String(help="XML data for the problem", + data = String( + help="XML data for the problem", default='', - scope=Scope.content) + scope=Scope.content + ) position = Integer(help="Current position in the video", scope=Scope.user_state, default=0) show_captions = Boolean(help="This controls whether or not captions are shown by default.", display_name="Show Captions", scope=Scope.settings, default=True) youtube_id_1_0 = String(help="This is the Youtube ID reference for the normal speed video.", display_name="Default Speed", scope=Scope.settings, default="OEoXaMPEzfM") diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 7a2afc8af1..6b409f677b 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -221,7 +221,7 @@ class TestTOC(TestCase): 'format': '', 'due': None, 'active': False}, {'url_name': 'video_123456789012', 'display_name': 'Test Video', 'graded': True, 'format': '', 'due': None, 'active': False}, - {'url_name': 'video_4f66f493ac8f', 'display_name': 'Video Title', 'graded': True, + {'url_name': 'video_4f66f493ac8f', 'display_name': 'Video', 'graded': True, 'format': '', 'due': None, 'active': False}], 'url_name': 'Overview', 'display_name': u'Overview'}, {'active': False, 'sections': @@ -230,7 +230,6 @@ class TestTOC(TestCase): 'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) actual = render.toc_for_course(self.portal_user, request, self.toy_course, chapter, None, model_data_cache) - print actual assert reduce(lambda x, y: x and (y in actual), expected, True) def test_toc_toy_from_section(self): @@ -249,7 +248,7 @@ class TestTOC(TestCase): 'format': '', 'due': None, 'active': True}, {'url_name': 'video_123456789012', 'display_name': 'Test Video', 'graded': True, 'format': '', 'due': None, 'active': False}, - {'url_name': 'video_4f66f493ac8f', 'display_name': 'Video Title', 'graded': True, + {'url_name': 'video_4f66f493ac8f', 'display_name': 'Video', 'graded': True, 'format': '', 'due': None, 'active': False}], 'url_name': 'Overview', 'display_name': u'Overview'}, {'active': False, 'sections': From 60d60de2c44dfb91f48a25df5a8e78e64be8a2fb Mon Sep 17 00:00:00 2001 From: cahrens Date: Thu, 25 Jul 2013 12:21:17 -0400 Subject: [PATCH 51/71] Use format for strings instead of %. --- cms/djangoapps/course_creators/models.py | 2 +- cms/templates/activation_active.html | 2 +- cms/templates/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/course_creators/models.py b/cms/djangoapps/course_creators/models.py index b370ac423a..4975705407 100644 --- a/cms/djangoapps/course_creators/models.py +++ b/cms/djangoapps/course_creators/models.py @@ -39,7 +39,7 @@ class CourseCreator(models.Model): "why course creation access was denied)")) def __unicode__(self): - return u'%s | %s [%s]' % (self.user, self.state, self.state_changed) + return u"{0} | {1} [{2}]".format(self.user, self.state, self.state_changed) @receiver(post_init, sender=CourseCreator) diff --git a/cms/templates/activation_active.html b/cms/templates/activation_active.html index f2232f6f40..d7133062de 100644 --- a/cms/templates/activation_active.html +++ b/cms/templates/activation_active.html @@ -17,7 +17,7 @@

    ${_("Your account is already active")}

    -

    ${_("This account, set up using (%(email)s), has already been activated. Please sign in to start working within edX Studio.") % dict(email=user.email)}

    +

    ${_("This account, set up using {0}, has already been activated. Please sign in to start working within edX Studio.".format(user.email))}

    diff --git a/cms/templates/index.html b/cms/templates/index.html index ba24099e63..586762eae5 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -95,7 +95,7 @@
    -

    ${_("Welcome, %(name)s!") % dict(name= user.username)}

    +

    ${_("Welcome, {0}!".format(user.username))}

    %if len(courses) > 0:
    From 628994daf11771153a21ea342f8f252f3a2a1c8d Mon Sep 17 00:00:00 2001 From: cahrens Date: Thu, 25 Jul 2013 12:25:06 -0400 Subject: [PATCH 52/71] i18n "Create a New Course". --- cms/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/templates/index.html b/cms/templates/index.html index 586762eae5..df3e467200 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -39,7 +39,7 @@ <%block name="header_extras">