From 29b8577346646085ae2aadff8661149ed32eef04 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Mon, 6 Aug 2012 10:13:52 -0400 Subject: [PATCH 01/34] Fix styles in the handouts sidebar --- lms/static/sass/course/_info.scss | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index d9af9c0f82..a29398450a 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -106,12 +106,13 @@ div.info-wrapper { @include box-sizing(border-box); padding: em(7) lh(.75); position: relative; + font-size: $body-font-size; &.expandable, &.collapsable { h4 { - font-style: $body-font-size; font-weight: normal; + font-size: 1em; padding-left: 18px; } } @@ -125,13 +126,10 @@ div.info-wrapper { border-top: 1px solid #d3d3d3; @include box-shadow(inset 0 1px 0 #eee); padding-left: lh(1.5); + font-size: 1em; } } - &:hover { - background-color: #e9e9e9; - } - div.hitarea { background-image: url('../images/treeview-default.gif'); display: block; @@ -159,20 +157,20 @@ div.info-wrapper { h3 { border-bottom: 0; @include box-shadow(none); - color: #999; - font-size: $body-font-size; - font-weight: bold; - text-transform: uppercase; + color: #aaa; + font-size: 1em; + margin-bottom: em(6); } p { - font-size: $body-font-size; + font: 1em $sans-serif; letter-spacing: 0; margin: 0; text-transform: none; a { padding-right: 8px; + font-family: $sans-serif; &:before { color: #ccc; From e40c6b381290e9d35b7cb015471e37cc981bbba6 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Mon, 6 Aug 2012 10:59:55 -0400 Subject: [PATCH 02/34] Reset some styles for the textbook --- lms/static/sass/course/_info.scss | 7 ++-- lms/static/sass/course/_textbook.scss | 47 ++++++++++++++--------- lms/static/sass/course/base/_extends.scss | 1 - lms/templates/staticbook.html | 11 ------ 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index a29398450a..5c19ea42d4 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -20,7 +20,7 @@ div.info-wrapper { > li { @extend .clearfix; - border-bottom: 1px solid #e3e3e3; + border-bottom: 1px solid lighten($border-color, 10%); margin-bottom: lh(); padding-bottom: lh(.5); list-style-type: disk; @@ -101,8 +101,7 @@ div.info-wrapper { li { @extend .clearfix; background: none; - border-bottom: 1px solid #d3d3d3; - @include box-shadow(0 1px 0 #eee); + border-bottom: 1px solid $border-color; @include box-sizing(border-box); padding: em(7) lh(.75); position: relative; @@ -123,7 +122,7 @@ div.info-wrapper { li { border-bottom: 0; - border-top: 1px solid #d3d3d3; + border-top: 1px solid $border-color; @include box-shadow(inset 0 1px 0 #eee); padding-left: lh(1.5); font-size: 1em; diff --git a/lms/static/sass/course/_textbook.scss b/lms/static/sass/course/_textbook.scss index ed5e528809..d6a30db949 100644 --- a/lms/static/sass/course/_textbook.scss +++ b/lms/static/sass/course/_textbook.scss @@ -7,7 +7,7 @@ div.book-wrapper { @include box-sizing(border-box); ul#booknav { - font-size: 12px; + font-size: $body-font-size; a { color: #000; @@ -39,8 +39,7 @@ div.book-wrapper { } > li { - border-bottom: 1px solid #d3d3d3; - @include box-shadow(0 1px 0 #eee); + border-bottom: 1px solid $border-color; padding: 7px 7px 7px 30px; } } @@ -48,9 +47,11 @@ div.book-wrapper { section.book { @extend .content; + padding-right: 0; + padding-bottom: 0; + padding-top: 0; nav { - @extend .topbar; @extend .clearfix; a { @@ -62,32 +63,41 @@ div.book-wrapper { @extend .clearfix; li { + position: absolute; + height: 100%; + width: flex-grid(2, 8); - &.last { - display: block; - float: left; + a { + display: table; + @include box-sizing(border-box); + height: 100%; + width: 100%; + padding-top: 200%; + vertical-align: middle; + @include transition; + background: rgba(#000, .7); + opacity: 0; + filter: alpha(opacity=0); - a { - border-left: 0; - border-right: 1px solid darken(#f6efd4, 20%); - @include box-shadow(inset -1px 0 0 lighten(#f6efd4, 5%)); + &:hover { + opacity: 1; + filter: alpha(opacity=100); } } - &.next { - display: block; - float: right; + &.last { + left: 0; } - &:hover { - background: none; + &.next { + right: 0; } + } } &.bottom-nav { border-bottom: 0; - border-top: 1px solid #EDDFAA; margin-bottom: -(lh()); margin-top: lh(); } @@ -95,9 +105,10 @@ div.book-wrapper { section.page { text-align: center; + position: relative; + border: 1px solid $border-color; img { - border: 1px solid $border-color; max-width: 100%; } } diff --git a/lms/static/sass/course/base/_extends.scss b/lms/static/sass/course/base/_extends.scss index 5927cb569e..a2280adaac 100644 --- a/lms/static/sass/course/base/_extends.scss +++ b/lms/static/sass/course/base/_extends.scss @@ -51,7 +51,6 @@ h1.top-header { .sidebar { border-right: 1px solid #C8C8C8; - @include box-shadow(inset -1px 0 0 #e6e6e6); @include box-sizing(border-box); display: table-cell; font-family: $sans-serif; diff --git a/lms/templates/staticbook.html b/lms/templates/staticbook.html index eae70cdd84..dbc5bb1cec 100644 --- a/lms/templates/staticbook.html +++ b/lms/templates/staticbook.html @@ -89,17 +89,6 @@ $("#open_close_accordion a").click(function(){ - - From a090d62339736ffe4b273576f4201d4f56fe80e2 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Mon, 6 Aug 2012 11:12:10 -0400 Subject: [PATCH 03/34] Add styles for next last page in textbook --- lms/static/sass/course/_textbook.scss | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lms/static/sass/course/_textbook.scss b/lms/static/sass/course/_textbook.scss index d6a30db949..249dadf29b 100644 --- a/lms/static/sass/course/_textbook.scss +++ b/lms/static/sass/course/_textbook.scss @@ -72,25 +72,41 @@ div.book-wrapper { @include box-sizing(border-box); height: 100%; width: 100%; - padding-top: 200%; vertical-align: middle; @include transition; - background: rgba(#000, .7); + background-color: rgba(#000, .7); + background-repeat: no-repeat; + background-position: center; opacity: 0; filter: alpha(opacity=0); + text-indent: -9999px; &:hover { opacity: 1; filter: alpha(opacity=100); + + &.last { + } + + &.next { + } } } &.last { left: 0; + + a { + background-image: url('../images/textbook/textbook-left.png'); + } } &.next { right: 0; + + a { + background-image: url('../images/textbook/textbook-right.png'); + } } } From ac960ab9df2f57048c72c7a881adefa2c804bcb6 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Mon, 6 Aug 2012 13:33:33 -0400 Subject: [PATCH 04/34] Add images for book and fix styles for wiki --- lms/static/images/textbook/textbook-left.png | Bin 0 -> 783 bytes lms/static/images/textbook/textbook-right.png | Bin 0 -> 822 bytes lms/static/sass/course/_info.scss | 17 +---- lms/static/sass/course/base/_extends.scss | 2 +- lms/static/sass/course/wiki/_sidebar.scss | 68 ++++++++---------- lms/templates/main.html | 2 +- lms/templates/simplewiki/simplewiki_base.html | 58 +++++++-------- 7 files changed, 63 insertions(+), 84 deletions(-) create mode 100644 lms/static/images/textbook/textbook-left.png create mode 100644 lms/static/images/textbook/textbook-right.png diff --git a/lms/static/images/textbook/textbook-left.png b/lms/static/images/textbook/textbook-left.png new file mode 100644 index 0000000000000000000000000000000000000000..52cb58efe7ca23394ce80ddb9eefa8990d49278d GIT binary patch literal 783 zcmeAS@N?(olHy`uVBq!ia0vp^zCb*kgAGUqthx4+fr06or;B4q#jUp&-1|fV1zZDF z(sWqfKdX3e>*4e9p68kt(G6@jn6(cc=D2=C&&VZp^^a5YyS_)6{xV-;-C=H{tu~qS zsnwmYYqCRM3vk_V3cvIDn&GB|`^yu!KW^t!YCd|;s*8s$O72a-n*~>`jab-baez=1 z-6G5IWHa8C5a1=i1u)4g211unO0dnu!@GqszZdx$FW%G?&5R zr%iu#fX-~JeRt4-d5u&;v70sjjr}nj-fnDWi?BM-!(c4M{M(fEH_MIxjA<-01QSj> z><(zi&6Z7DBlpHULF!hw=8dH5N3S_<%IM$ytNP~FU8PfaqP}og$m&*EFn{OTQE2jj z2Z**v-BBuFiek<4eI2nmjK+(&Bd}g46a-aeqFtXY4b-~kJv4k5(TK8I2CPVIy z8r0aLSZ$d0W}wN_US|Qdvu|fL5$1!SEivIhH>HkM6v(? literal 0 HcmV?d00001 diff --git a/lms/static/images/textbook/textbook-right.png b/lms/static/images/textbook/textbook-right.png new file mode 100644 index 0000000000000000000000000000000000000000..8779df5df54704b0a6a0fb88f99af551a0ef5adf GIT binary patch literal 822 zcmeAS@N?(olHy`uVBq!ia0vp^zCb*kgAGUqthx4+fr06-r;B4q#jUq@J^iKxinJvP zrRTY9Ji2qmjimnALs#~>``+oWF5rK6aPAF-J_ptujsMc!PpX3MSlypK>#zRF(+k?p zGpu^G|NESk2NO1|ew};$?K(}V_@7^wt^V_eQLn#j$!qNbDX9Xrs7$p3cVAljar0U* zL(p2*?M5xPncmf#Jm7&M@ppO|JNcNu^8;1n3Ke{JIDNQwp2vF#p!{>)`4QYoOr@c;SVLl5%&D9*UIN77tF$?mU@V^txP?O2n`OV$pf)5zk z>^Q#Q>BDK+P>%Y7nFS#9^p^NT2jhnhpC8O?n%4e1=5Qm>)Mp26p@!;SFM%1(2Qpml zT`({EEcWdzKn?^&t=kK>j}>HJ<&J(+nGCr*-cU<{Ru{ePELiZepqUA#UhbVU>vt{_ zVLlMze^+|nag%OYF7L&A-({6AUtD)JYsHJT`o?!exRzn`Is! z+;=r$j+D?ri}}kM?>;m#px{%?EP z9`9SuRyKF`Q6-7;8?FY+RJT~m9av{L-I*nssfXj(8b!gLm0@rFTnwHq&rIOX@*t{F4^+gl4DQp4*r3Mn~-6jlQ*wu_^q!IgTBxXXe>5*V>qi z$K(Ku3GW-HN?DTwYHej@A`%&u8$LfRy})qW@IViPanOM}o`YuF4mvRF$R@Zo_zJv@ zVVM2iIt0X#WK1*R%8}hrc01v9LuST%hRYYr#4Qed`pa4*eIm@2-}?eEKQMT@`njxg HN@xNAGDl@n literal 0 HcmV?d00001 diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 5c19ea42d4..b574eab48e 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -76,27 +76,14 @@ div.info-wrapper { h1 { @extend .bottom-border; padding: lh(.5) lh(.5); - } - - header { - - // h1 { - // font-weight: 100; - // font-style: italic; - // } - - p { - color: #666; - font-size: 12px; - margin-bottom: 0; - margin-top: 4px; - } + margin-bottom: 0; } ol { background: none; list-style: none; padding-left: 0; + margin: 0; li { @extend .clearfix; diff --git a/lms/static/sass/course/base/_extends.scss b/lms/static/sass/course/base/_extends.scss index a2280adaac..9f8cb9fa35 100644 --- a/lms/static/sass/course/base/_extends.scss +++ b/lms/static/sass/course/base/_extends.scss @@ -74,7 +74,7 @@ h1.top-header { } .bottom-border { - border-bottom: 1px solid #d3d3d3; + border-bottom: 1px solid $border-color; } @media print { diff --git a/lms/static/sass/course/wiki/_sidebar.scss b/lms/static/sass/course/wiki/_sidebar.scss index 90bc654e08..ccaed19a17 100644 --- a/lms/static/sass/course/wiki/_sidebar.scss +++ b/lms/static/sass/course/wiki/_sidebar.scss @@ -4,78 +4,70 @@ div#wiki_panel { h2 { @extend .bottom-border; - font-size: 18px; margin: 0 ; - padding: lh(.5) lh(); - } - - input[type="button"] { - background: transparent; - border: none; - @include box-shadow(none); - color: #666; - font-size: 14px; - font-weight: bold; - margin: 0px; - padding: 7px lh(); - text-align: left; - @include transition(); - width: 100%; + padding: lh(.5) lh() lh(.5) 0; + color: #000; } ul { + padding-left: 0; + margin: 0; + li { - @include box-shadow(inset 0 1px 0 0 #eee); - border-top: 1px solid #d3d3d3; - - &:hover { - background: #efefef; - @include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(225,225,225))); - } - - &:first-child { - border: none; - } + @extend .bottom-border; &.search { - padding: 10px lh(); + padding: 10px lh() 10px 0; label { display: none; } } - &.create-article { - h3 { - } - } - a { color: #666; font-size: 14px; - padding: 7px lh(); + padding: 7px lh() 7px 0; + + &:hover { + background: #efefef; + } + + } + } + + form { + input[type="submit"]{ + @extend .light-button; + text-transform: none; + text-shadow: none; } } } div#wiki_create_form { @extend .clearfix; - background: #dadada; - border-bottom: 1px solid #d3d3d3; - padding: 15px; + padding: lh(.5) lh() lh(.5) 0; + + label { + font-family: $sans-serif; + margin-bottom: lh(.5); + } input[type="text"] { @include box-sizing(border-box); display: block; - margin-bottom: 6px; width: 100%; + margin-bottom: lh(.5); } ul { list-style: none; + margin: 0; li { float: left; + border-bottom: 0; &#cancel { float: right; diff --git a/lms/templates/main.html b/lms/templates/main.html index fb502bfe22..891d770637 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -25,9 +25,9 @@ <%include file="navigation.html" />
${self.body()} + <%block name="bodyextra"/>
- <%block name="bodyextra"/> <%include file="footer.html" /> <%static:js group='application'/> diff --git a/lms/templates/simplewiki/simplewiki_base.html b/lms/templates/simplewiki/simplewiki_base.html index 6777af1b82..04a239b6c3 100644 --- a/lms/templates/simplewiki/simplewiki_base.html +++ b/lms/templates/simplewiki/simplewiki_base.html @@ -71,9 +71,9 @@ }); - + <%block name="wiki_head"/> - + <%block name="bodyextra"> @@ -86,7 +86,7 @@
<%block name="wiki_panel">
-

Course Wiki

+

Course Wiki

  • @@ -101,12 +101,12 @@
    <% - baseURL = wiki_reverse("wiki_create", course=course, kwargs={"article_path" : namespace + "/" }) + baseURL = wiki_reverse("wiki_create", course=course, kwargs={"article_path" : namespace + "/" }) %>
    -
    +
    • @@ -130,31 +130,31 @@
      - %if wiki_article is not UNDEFINED: -
      - %if wiki_article.locked: -

      This article has been locked

      - %endif -

      Last modified: ${wiki_article.modified_on.strftime("%b %d, %Y, %I:%M %p")}

      - %endif - - %if wiki_article is not UNDEFINED: - -
      + %if wiki_article is not UNDEFINED: +
      + %if wiki_article.locked: +

      This article has been locked

      %endif +

      Last modified: ${wiki_article.modified_on.strftime("%b %d, %Y, %I:%M %p")}

      + %endif + + %if wiki_article is not UNDEFINED: + +
      + %endif <%block name="wiki_page_title"/> <%block name="wiki_body"/> From e90103abdf239a82d6a4683817c5cce22e0fa164 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Mon, 6 Aug 2012 16:03:48 -0400 Subject: [PATCH 05/34] Reset styles for profile page and some for the info page --- lms/static/sass/course/_info.scss | 13 +---- lms/static/sass/course/_profile.scss | 65 +++++++++++++++-------- lms/static/sass/course/base/_base.scss | 6 +-- lms/static/sass/course/base/_extends.scss | 2 +- lms/templates/profile.html | 30 +++++++---- 5 files changed, 65 insertions(+), 51 deletions(-) diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index b574eab48e..6b217f2285 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -92,7 +92,7 @@ div.info-wrapper { @include box-sizing(border-box); padding: em(7) lh(.75); position: relative; - font-size: $body-font-size; + font-size: 1em; &.expandable, &.collapsable { @@ -111,7 +111,6 @@ div.info-wrapper { border-bottom: 0; border-top: 1px solid $border-color; @include box-shadow(inset 0 1px 0 #eee); - padding-left: lh(1.5); font-size: 1em; } } @@ -149,14 +148,12 @@ div.info-wrapper { } p { - font: 1em $sans-serif; letter-spacing: 0; margin: 0; text-transform: none; a { padding-right: 8px; - font-family: $sans-serif; &:before { color: #ccc; @@ -175,14 +172,8 @@ div.info-wrapper { } a { - color: lighten($text-color, 10%); @include inline-block(); - text-decoration: none; - @include transition(); - - &:hover { - color: $mit-red; - } + line-height: lh(); } } } diff --git a/lms/static/sass/course/_profile.scss b/lms/static/sass/course/_profile.scss index a772f1a293..006bd902e5 100644 --- a/lms/static/sass/course/_profile.scss +++ b/lms/static/sass/course/_profile.scss @@ -10,38 +10,26 @@ div.profile-wrapper { header { @extend .bottom-border; - margin: 0 ; - padding: lh(.5) lh(); + margin: 0; + padding: lh(.5); h1 { - font-size: 18px; margin: 0; padding-right: 30px; } - - a { - color: #999; - font-size: 12px; - position: absolute; - right: lh(.5); - text-transform: uppercase; - top: 13px; - - &:hover { - color: #555; - } - } } ul { list-style: none; + padding: 0; + margin: 0; li { border-bottom: 1px solid #d3d3d3; @include box-shadow(0 1px 0 #eee); color: lighten($text-color, 10%); display: block; - padding: 7px lh(); + padding: lh(.5) 0 lh(.5) lh(.5); position: relative; text-decoration: none; @include transition(); @@ -144,11 +132,14 @@ div.profile-wrapper { @extend .content; header { - @extend h1.top-header; @extend .clearfix; + @extend h1.top-header; + margin-bottom: lh(); h1 { float: left; + font-size: 1em; + font-weight: 100; margin: 0; } } @@ -162,6 +153,7 @@ div.profile-wrapper { border-top: 1px solid #e3e3e3; list-style: none; margin-top: lh(); + padding-left: 0; > li { @extend .clearfix; @@ -178,9 +170,11 @@ div.profile-wrapper { border-right: 1px dashed #ddd; @include box-sizing(border-box); display: table-cell; + letter-spacing: 0; margin: 0; padding: 0; padding-right: flex-gutter(9); + text-transform: none; width: flex-grid(2, 9); } @@ -203,14 +197,39 @@ div.profile-wrapper { h3 { color: #666; + + span { + color: #999; + font-size: em(14); + font-weight: 100; + } } - ol { - list-style: none; + p { + color: #999; + font-size: em(14); + } - li { - display: inline-block; - padding-right: 1em; + section.scores { + margin: lh(.5) 0; + + h3 { + font-size: em(14); + @include inline-block; + } + + ol { + list-style: none; + margin: 0; + padding: 0; + @include inline-block; + + li { + @include inline-block; + font-size: em(14); + font-weight: normal; + padding-right: 1em; + } } } } diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index d04bcab103..aa8ab3db84 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -1,8 +1,4 @@ -body { - font-family: $sans-serif; -} - -h1, h2, h3, h4, h5, h6 { +body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a { font-family: $sans-serif; } diff --git a/lms/static/sass/course/base/_extends.scss b/lms/static/sass/course/base/_extends.scss index 9f8cb9fa35..a0d417f388 100644 --- a/lms/static/sass/course/base/_extends.scss +++ b/lms/static/sass/course/base/_extends.scss @@ -1,7 +1,7 @@ h1.top-header { border-bottom: 1px solid #e3e3e3; text-align: left; - font-size: 24px; + font-size: em(24); font-weight: 100; padding-bottom: lh(); } diff --git a/lms/templates/profile.html b/lms/templates/profile.html index 8107bb1923..5a71946d83 100644 --- a/lms/templates/profile.html +++ b/lms/templates/profile.html @@ -139,19 +139,27 @@ $(function() { %>

      - ${ section['display_name'] } ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}

      - ${section['format']} - %if 'due' in section and section['due']!="": - due ${section['due']} - %endif + ${ section['display_name'] } ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}

    +

    + ${section['format']} + + %if 'due' in section and section['due']!="": + + due ${section['due']} + + %endif +

    + %if len(section['scores']) > 0: -
      - ${ "Problem Scores: " if section['graded'] else "Practice Scores: "} - %for score in section['scores']: -
    1. ${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}
    2. - %endfor -
    +
    +

    ${ "Problem Scores: " if section['graded'] else "Practice Scores: "}

    +
      + %for score in section['scores']: +
    1. ${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}
    2. + %endfor +
    +
    %endif
  • From 99c3a7996ad46009698350cdfe64cc85fbacc205 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Mon, 6 Aug 2012 16:49:29 -0400 Subject: [PATCH 06/34] Fix color of speed dropdown --- common/lib/xmodule/xmodule/css/video/display.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index 789a267755..bb69c30abb 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -207,7 +207,7 @@ div.video { h3 { color: #999; float: left; - font-size: 12px; + font-size: em(14); font-weight: normal; letter-spacing: 1px; padding: 0 lh(.25) 0 lh(.5); @@ -221,6 +221,7 @@ div.video { margin-bottom: 0; padding: 0 lh(.5) 0 0; line-height: 46px; + color: #fff; } &:hover, &:active, &:focus { From d3f4746f572fb393425dbe597f0bbab9e59570e6 Mon Sep 17 00:00:00 2001 From: kimth Date: Mon, 6 Aug 2012 19:02:40 -0400 Subject: [PATCH 07/34] Don't contact queueing server on Django load --- common/lib/capa/capa/xqueue_interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py index 82e4fba5cc..70f086120e 100644 --- a/common/lib/capa/capa/xqueue_interface.py +++ b/common/lib/capa/capa/xqueue_interface.py @@ -67,7 +67,6 @@ class XqueueInterface: self.url = url self.auth = auth self.session = requests.session() - self._login() def send_to_queue(self, header, body, file_to_upload=None): ''' From fa8bf7f371e5b6be2e70658e66a46694f2bf84be Mon Sep 17 00:00:00 2001 From: ichuang Date: Mon, 6 Aug 2012 21:54:29 -0400 Subject: [PATCH 08/34] make the staff debug information appear in a show/hide span --- lms/templates/staff_problem_info.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lms/templates/staff_problem_info.html b/lms/templates/staff_problem_info.html index c9b92c51db..483acbd9cf 100644 --- a/lms/templates/staff_problem_info.html +++ b/lms/templates/staff_problem_info.html @@ -2,9 +2,17 @@ ${module_content} %if edit_link: % endif + + +
    + +
    %if render_histogram:
    From 96746721c43f245ba135ba64937f21a763e90222 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Tue, 7 Aug 2012 00:11:39 -0400 Subject: [PATCH 09/34] Fixed incorrect default argument so that tests run; however the new value is incorrect and needs to be fixed. --- common/lib/xmodule/xmodule/tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index 7f6fcfe00c..7b1641956c 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -31,7 +31,7 @@ i4xs = ModuleSystem( user=Mock(), filestore=fs.osfs.OSFS(os.path.dirname(os.path.realpath(__file__))), debug=True, - xqueue_callback_url='/', + xqueue=None, # TODO FIXME is_staff=False ) From 323fb18c7461b4574c88926f6488952ee3e89071 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Tue, 7 Aug 2012 00:14:48 -0400 Subject: [PATCH 10/34] Poor fix for a breaking change in which student answers that were arrays (e.g. those resulting from a checkbox group) would be converted to strings and graded incorrectly. --- common/lib/capa/capa/util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/util.py b/common/lib/capa/capa/util.py index 1dc113cd20..211ada4d62 100644 --- a/common/lib/capa/capa/util.py +++ b/common/lib/capa/capa/util.py @@ -39,5 +39,10 @@ def convert_files_to_filenames(answers): ''' new_answers = dict() for answer_id in answers.keys(): - new_answers[answer_id] = unicode(answers[answer_id]) + # TODO This should be done more cleanly; however, this fixes bugs + # that were introduced with this function. + if isinstance(answers[answer_id], list): + new_answers[answer_id] = answers[answer_id] + else: + new_answers[answer_id] = unicode(answers[answer_id]) return new_answers From 19fa50a882abd0d0a4834d66550572edb379499e Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Tue, 7 Aug 2012 02:38:23 -0400 Subject: [PATCH 11/34] Removing the wrapper around choice text for radiogroup and checkboxgroup. --- common/lib/capa/capa/inputtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 583d29b82e..5092e5c378 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -204,7 +204,7 @@ def extract_choices(element): raise Exception("[courseware.capa.inputtypes.extract_choices] \ Expected a tag; got %s instead" % choice.tag) - choice_text = ''.join([etree.tostring(x) for x in choice]) + choice_text = ''.join([x.text for x in choice]) choices.append((choice.get("name"), choice_text)) From ceeea66417832a21d1a7fa17e9e1ed8d4ee87d89 Mon Sep 17 00:00:00 2001 From: kimth Date: Tue, 7 Aug 2012 09:24:17 -0400 Subject: [PATCH 12/34] Fix overrzealous file_to_filename conversion and fix CapaProblem unit tests --- common/lib/capa/capa/util.py | 18 +++++++++++++----- common/lib/xmodule/xmodule/tests/__init__.py | 2 +- common/lib/xmodule/xmodule/x_module.py | 7 +++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/common/lib/capa/capa/util.py b/common/lib/capa/capa/util.py index 211ada4d62..98c250297d 100644 --- a/common/lib/capa/capa/util.py +++ b/common/lib/capa/capa/util.py @@ -39,10 +39,18 @@ def convert_files_to_filenames(answers): ''' new_answers = dict() for answer_id in answers.keys(): - # TODO This should be done more cleanly; however, this fixes bugs - # that were introduced with this function. - if isinstance(answers[answer_id], list): - new_answers[answer_id] = answers[answer_id] + if is_uploaded_file(answers[answer_id]): + new_answers[answer_id] = answers[answer_id].name else: - new_answers[answer_id] = unicode(answers[answer_id]) + new_answers[answer_id] = answers[answer_id] return new_answers + +def is_uploaded_file(file_to_test): + ''' + Duck typing to check if 'file_to_test' is a File object + ''' + is_file = True + for method in ['read', 'name']: + if not hasattr(file_to_test, method): + is_file = False + return is_file diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index 7b1641956c..072abb4908 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -31,7 +31,7 @@ i4xs = ModuleSystem( user=Mock(), filestore=fs.osfs.OSFS(os.path.dirname(os.path.realpath(__file__))), debug=True, - xqueue=None, # TODO FIXME + xqueue=None, is_staff=False ) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index f013aac785..9b005d1dae 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -643,7 +643,7 @@ class ModuleSystem(object): user=None, filestore=None, debug=False, - xqueue = None, + xqueue=None, is_staff=False): ''' Create a closure around the system environment. @@ -678,7 +678,10 @@ class ModuleSystem(object): TODO (vshnayder): this will need to change once we have real user roles. ''' self.ajax_url = ajax_url - self.xqueue = xqueue + if xqueue is None: + self.xqueue = {'interface':None, 'callback_url':'/', 'default_queuename':'null'} + else: + self.xqueue = xqueue self.track_function = track_function self.filestore = filestore self.get_module = get_module From cb2fbfa54c5b16696859dd84d33226deb9887f9c Mon Sep 17 00:00:00 2001 From: kimth Date: Tue, 7 Aug 2012 09:38:36 -0400 Subject: [PATCH 13/34] Added test for answers file-to-filename conversion --- common/lib/xmodule/xmodule/tests/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index 072abb4908..4c0b6fc9b3 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -15,6 +15,7 @@ import xmodule import capa.calc as calc import capa.capa_problem as lcp from capa.correctmap import CorrectMap +from capa.util import convert_files_to_filenames from xmodule import graders, x_module from xmodule.x_module import ModuleSystem from xmodule.graders import Score, aggregate_scores @@ -278,7 +279,6 @@ class StringResponseWithHintTest(unittest.TestCase): class CodeResponseTest(unittest.TestCase): ''' Test CodeResponse - ''' def test_update_score(self): problem_file = os.path.dirname(__file__) + "/test_files/coderesponse.xml" @@ -327,7 +327,18 @@ class CodeResponseTest(unittest.TestCase): self.assertFalse(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be dequeued, message delivered else: self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be queued, message undelivered - + + def test_convert_files_to_filenames(self): + problem_file = os.path.dirname(__file__) + "/test_files/coderesponse.xml" + fp = open(problem_file) + answers_with_file = {'1_2_1': 'String-based answer', + '1_3_1': ['answer1', 'answer2', 'answer3'], + '1_4_1': fp} + answers_converted = convert_files_to_filenames(answers_with_file) + self.assertEquals(answers_converted['1_2_1'], 'String-based answer') + self.assertEquals(answers_converted['1_3_1'], ['answer1', 'answer2', 'answer3']) + self.assertEquals(answers_converted['1_4_1'], fp.name) + class ChoiceResponseTest(unittest.TestCase): From 4b2091b17326f4a7be669c6723d5b603e95a7baf Mon Sep 17 00:00:00 2001 From: ichuang Date: Tue, 7 Aug 2012 10:09:55 -0400 Subject: [PATCH 14/34] fix typo in registration button jquery --- lms/templates/portal/course_about.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/portal/course_about.html b/lms/templates/portal/course_about.html index a3bf8dd755..36ec33607f 100644 --- a/lms/templates/portal/course_about.html +++ b/lms/templates/portal/course_about.html @@ -20,7 +20,7 @@ if(json.success) { location.href="${reverse('dashboard')}"; }else{ - $('#register_message).html("

    " + json.error + "

    ") + $('#register_message').html("

    " + json.error + "

    "); } }); })(this) From d09e2261f3ba92e5a248de466c7c4ef5aa230be7 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 3 Aug 2012 14:51:19 -0400 Subject: [PATCH 15/34] New file structure--everything in own file * needed for CMS performance (can now save just an item, not whole tree) * remove split_to_file methods * simplified AttrMap logic * write each descriptor to a separate file * detect format on import and adjust appropriately. * update tests --- common/lib/xmodule/xmodule/capa_module.py | 5 - common/lib/xmodule/xmodule/html_module.py | 8 +- common/lib/xmodule/xmodule/seq_module.py | 13 -- .../lib/xmodule/xmodule/tests/test_import.py | 9 +- common/lib/xmodule/xmodule/xml_module.py | 166 +++++++++--------- 5 files changed, 93 insertions(+), 108 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index a384edf234..833713fcde 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -572,8 +572,3 @@ class CapaDescriptor(RawDescriptor): 'problems/' + path[8:], path[8:], ] - - @classmethod - def split_to_file(cls, xml_object): - '''Problems always written in their own files''' - return True diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index 260b84278b..de45af081a 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -109,17 +109,11 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor): # add more info and re-raise raise Exception(msg), None, sys.exc_info()[2] - @classmethod - def split_to_file(cls, xml_object): - '''Never include inline html''' - return True - - # TODO (vshnayder): make export put things in the right places. def definition_to_xml(self, resource_fs): '''If the contents are valid xml, write them to filename.xml. Otherwise, - write just the tag to filename.xml, and the html + write just to filename.xml, and the html string to filename.html. ''' try: diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 5f7f41bf8d..e742a58471 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -122,16 +122,3 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor): etree.fromstring(child.export_to_xml(resource_fs))) return xml_object - @classmethod - def split_to_file(cls, xml_object): - # Note: if we end up needing subclasses, can port this logic there. - yes = ('chapter',) - no = ('course',) - - if xml_object.tag in yes: - return True - elif xml_object.tag in no: - return False - - # otherwise maybe--delegate to superclass. - return XmlDescriptor.split_to_file(xml_object) diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py index 0d3e2260fb..2599a74079 100644 --- a/common/lib/xmodule/xmodule/tests/test_import.py +++ b/common/lib/xmodule/xmodule/tests/test_import.py @@ -134,7 +134,8 @@ class ImportTestCase(unittest.TestCase): resource_fs = MemoryFS() exported_xml = descriptor.export_to_xml(resource_fs) print "Exported xml:", exported_xml - root = etree.fromstring(exported_xml) - chapter_tag = root[0] - self.assertEqual(chapter_tag.tag, 'chapter') - self.assertFalse('graceperiod' in chapter_tag.attrib) + # hardcode path to child + with resource_fs.open('chapter/ch.xml') as f: + chapter_xml = etree.fromstring(f.read()) + self.assertEqual(chapter_xml.tag, 'chapter') + self.assertFalse('graceperiod' in chapter_xml.attrib) diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index 7a12ed869d..fd48439e46 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -11,22 +11,21 @@ import sys log = logging.getLogger(__name__) -_AttrMapBase = namedtuple('_AttrMap', 'metadata_key to_metadata from_metadata') +_AttrMapBase = namedtuple('_AttrMap', 'from_xml to_xml') class AttrMap(_AttrMapBase): """ - A class that specifies a metadata_key, and two functions: + A class that specifies two functions: - to_metadata: convert value from the xml representation into + from_xml: convert value from the xml representation into an internal python representation - from_metadata: convert the internal python representation into + to_xml: convert the internal python representation into the value to store in the xml. """ - def __new__(_cls, metadata_key, - to_metadata=lambda x: x, - from_metadata=lambda x: x): - return _AttrMapBase.__new__(_cls, metadata_key, to_metadata, from_metadata) + def __new__(_cls, from_xml=lambda x: x, + to_xml=lambda x: x): + return _AttrMapBase.__new__(_cls, from_xml, to_xml) class XmlDescriptor(XModuleDescriptor): @@ -39,6 +38,9 @@ class XmlDescriptor(XModuleDescriptor): # The attributes will be removed from the definition xml passed # to definition_from_xml, and from the xml returned by definition_to_xml + + # Note -- url_name isn't in this list because it's handled specially on + # import and export. metadata_attributes = ('format', 'graceperiod', 'showanswer', 'rerandomize', 'start', 'due', 'graded', 'display_name', 'url_name', 'hide_from_toc', 'ispublic', # if True, then course is listed for all users; see @@ -50,8 +52,7 @@ class XmlDescriptor(XModuleDescriptor): # to import and export them xml_attribute_map = { # type conversion: want True/False in python, "true"/"false" in xml - 'graded': AttrMap('graded', - lambda val: val == 'true', + 'graded': AttrMap(lambda val: val == 'true', lambda val: str(val).lower()), } @@ -101,6 +102,24 @@ class XmlDescriptor(XModuleDescriptor): """ return etree.parse(file_object).getroot() + @classmethod + def load_file(cls, filepath, fs, location): + ''' + Open the specified file in fs, and call cls.file_to_xml on it, + returning the lxml object. + + Add details and reraise on error. + ''' + try: + with fs.open(filepath) as file: + return cls.file_to_xml(file) + except Exception as err: + # Add info about where we are, but keep the traceback + msg = 'Unable to load file contents at path %s for item %s: %s ' % ( + filepath, location.url(), str(err)) + raise Exception, msg, sys.exc_info()[2] + + @classmethod def load_definition(cls, xml_object, system, location): '''Load a descriptor definition from the specified xml_object. @@ -128,14 +147,7 @@ class XmlDescriptor(XModuleDescriptor): filepath = candidate break - try: - with system.resources_fs.open(filepath) as file: - definition_xml = cls.file_to_xml(file) - except Exception: - msg = 'Unable to load file contents at path %s for item %s' % ( - filepath, location.url()) - # Add info about where we are, but keep the traceback - raise Exception, msg, sys.exc_info()[2] + definition_xml = cls.load_file(filepath, system.resources_fs, location) cls.clean_metadata_from_xml(definition_xml) definition = cls.definition_from_xml(definition_xml, system) @@ -146,6 +158,24 @@ class XmlDescriptor(XModuleDescriptor): return definition + @classmethod + def load_metadata(cls, xml_object): + """ + Read the metadata attributes from this xml_object. + + Returns a dictionary {key: value}. + """ + metadata = {} + for attr in cls.metadata_attributes: + val = xml_object.get(attr) + if val is not None: + # VS[compat]. Remove after all key translations done + attr = cls._translate(attr) + + attr_map = cls.xml_attribute_map.get(attr, AttrMap()) + metadata[attr] = attr_map.from_xml(val) + return metadata + @classmethod def from_xml(cls, xml_data, system, org=None, course=None): @@ -161,25 +191,20 @@ class XmlDescriptor(XModuleDescriptor): """ xml_object = etree.fromstring(xml_data) # VS[compat] -- just have the url_name lookup once translation is done - slug = xml_object.get('url_name', xml_object.get('slug')) - location = Location('i4x', org, course, xml_object.tag, slug) + url_name = xml_object.get('url_name', xml_object.get('slug')) + location = Location('i4x', org, course, xml_object.tag, url_name) - def load_metadata(): - metadata = {} - for attr in cls.metadata_attributes: - val = xml_object.get(attr) - if val is not None: - # VS[compat]. Remove after all key translations done - attr = cls._translate(attr) + # VS[compat] -- detect new-style each-in-a-file mode + if len(xml_object.attrib.keys()) == 1 and len(xml_object) == 0: + # new style: this is just a pointer. + # read the actual defition file--named using url_name + filepath = cls._format_filepath(xml_object.tag, url_name) + definition_xml = cls.load_file(filepath, system.resources_fs, location) + else: + definition_xml = xml_object - attr_map = cls.xml_attribute_map.get(attr, AttrMap(attr)) - metadata[attr_map.metadata_key] = attr_map.to_metadata(val) - return metadata - - definition = cls.load_definition(xml_object, system, location) - metadata = load_metadata() - # VS[compat] -- just have the url_name lookup once translation is done - slug = xml_object.get('url_name', xml_object.get('slug')) + definition = cls.load_definition(definition_xml, system, location) + metadata = cls.load_metadata(definition_xml) return cls( system, definition, @@ -193,20 +218,6 @@ class XmlDescriptor(XModuleDescriptor): name=name, ext=cls.filename_extension) - @classmethod - def split_to_file(cls, xml_object): - ''' - Decide whether to write this object to a separate file or not. - - xml_object: an xml definition of an instance of cls. - - This default implementation will split if this has more than 7 - descendant tags. - - Can be overridden by subclasses. - ''' - return len(list(xml_object.iter())) > 7 - def export_to_xml(self, resource_fs): """ Returns an xml string representing this module, and all modules @@ -227,42 +238,39 @@ class XmlDescriptor(XModuleDescriptor): xml_object = self.definition_to_xml(resource_fs) self.__class__.clean_metadata_from_xml(xml_object) - # Set the tag first, so it's right if writing to a file + # Set the tag so we get the file path right xml_object.tag = self.category - # Write it to a file if necessary - if self.split_to_file(xml_object): - # Put this object in its own file - filepath = self.__class__._format_filepath(self.category, self.url_name) - resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) - with resource_fs.open(filepath, 'w') as file: - file.write(etree.tostring(xml_object, pretty_print=True)) - # ...and remove all of its children here - for child in xml_object: - xml_object.remove(child) - # also need to remove the text of this object. - xml_object.text = '' - # and the tail for good measure... - xml_object.tail = '' + def val_for_xml(attr): + """Get the value for this attribute that we want to store. + (Possible format conversion through an AttrMap). + """ + attr_map = self.xml_attribute_map.get(attr, AttrMap()) + return attr_map.to_xml(self.own_metadata[attr]) - - xml_object.set('filename', self.url_name) - - # Add the metadata - xml_object.set('url_name', self.url_name) - for attr in self.metadata_attributes: - attr_map = self.xml_attribute_map.get(attr, AttrMap(attr)) - metadata_key = attr_map.metadata_key - - if (metadata_key not in self.metadata or - metadata_key in self._inherited_metadata): + # Add the non-inherited metadata + for attr in self.own_metadata: + if attr not in self.metadata_attributes: + log.warning("Unexpected metadata '{attr}' on element '{url}'." + " Not exporting.".format(attr=attr, + url=self.location.url())) continue - val = attr_map.from_metadata(self.metadata[metadata_key]) - xml_object.set(attr, val) + xml_object.set(attr, val_for_xml(attr)) - # Now we just have to make it beautiful - return etree.tostring(xml_object, pretty_print=True) + + # Write the actual contents to a file + filepath = self.__class__._format_filepath(self.category, self.url_name) + resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) + + with resource_fs.open(filepath, 'w') as file: + file.write(etree.tostring(xml_object, pretty_print=True)) + + # And return just a pointer with the category and filename. + record_object = etree.Element(self.category) + record_object.set('url_name', self.url_name) + + return etree.tostring(record_object, pretty_print=True) def definition_to_xml(self, resource_fs): """ From b285f50d92f02b197f16c0ee448ae26436c73397 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 3 Aug 2012 15:28:48 -0400 Subject: [PATCH 16/34] Make unknown metadata persist accross import-export * +improve test. --- .../lib/xmodule/xmodule/tests/test_import.py | 40 ++++++++++++++----- common/lib/xmodule/xmodule/xml_module.py | 17 ++++---- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py index 2599a74079..45a142b8fe 100644 --- a/common/lib/xmodule/xmodule/tests/test_import.py +++ b/common/lib/xmodule/xmodule/tests/test_import.py @@ -111,30 +111,50 @@ class ImportTestCase(unittest.TestCase): xml_out = etree.fromstring(xml_str_out) self.assertEqual(xml_out.tag, 'sequential') - def test_metadata_inherit(self): - """Make sure metadata inherits properly""" + def test_metadata_import_export(self): + """Two checks: + - unknown metadata is preserved across import-export + - inherited metadata doesn't leak to children. + """ system = self.get_system() v = "1 hour" - start_xml = ''' - - Two houses, ... - '''.format(grace=v) + start_xml = ''' + + + Two houses, ... + + '''.format(grace=v) descriptor = XModuleDescriptor.load_from_xml(start_xml, system, 'org', 'course') - print "Errors: {0}".format(system.errorlog.errors) print descriptor, descriptor.metadata self.assertEqual(descriptor.metadata['graceperiod'], v) + self.assertEqual(descriptor.metadata['unicorn'], 'purple') - # Check that the child inherits correctly + # Check that the child inherits graceperiod correctly child = descriptor.get_children()[0] self.assertEqual(child.metadata['graceperiod'], v) - # Now export and see if the chapter tag has a graceperiod attribute + # check that the child does _not_ inherit any unicorns + self.assertTrue('unicorn' not in child.metadata) + + # Now export and check things resource_fs = MemoryFS() exported_xml = descriptor.export_to_xml(resource_fs) print "Exported xml:", exported_xml - # hardcode path to child + + # Does the course still have unicorns? + # hardcoded path to course + with resource_fs.open('course/test1.xml') as f: + course_xml = etree.fromstring(f.read()) + + self.assertEqual(course_xml.attrib['unicorn'], 'purple') + + # did we successfully strip the url_name from the definition contents? + self.assertTrue('url_name' not in course_xml.attrib) + + # Does the chapter tag now have a graceperiod attribute? + # hardcoded path to child with resource_fs.open('chapter/ch.xml') as f: chapter_xml = etree.fromstring(f.read()) self.assertEqual(chapter_xml.tag, 'chapter') diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index fd48439e46..907fc302a3 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -47,6 +47,10 @@ class XmlDescriptor(XModuleDescriptor): # VS[compat] Remove once unused. 'name', 'slug') + # VS[compat] -- remove once everything is in the CMS + # We don't want url_name in the metadata--it's in the location, so avoid + # confusion and duplication. + metadata_to_strip = ('url_name', ) # A dictionary mapping xml attribute names AttrMaps that describe how # to import and export them @@ -166,12 +170,16 @@ class XmlDescriptor(XModuleDescriptor): Returns a dictionary {key: value}. """ metadata = {} - for attr in cls.metadata_attributes: + for attr in xml_object.attrib: val = xml_object.get(attr) if val is not None: # VS[compat]. Remove after all key translations done attr = cls._translate(attr) + if attr in cls.metadata_to_strip: + # don't load these + continue + attr_map = cls.xml_attribute_map.get(attr, AttrMap()) metadata[attr] = attr_map.from_xml(val) return metadata @@ -250,15 +258,8 @@ class XmlDescriptor(XModuleDescriptor): # Add the non-inherited metadata for attr in self.own_metadata: - if attr not in self.metadata_attributes: - log.warning("Unexpected metadata '{attr}' on element '{url}'." - " Not exporting.".format(attr=attr, - url=self.location.url())) - continue - xml_object.set(attr, val_for_xml(attr)) - # Write the actual contents to a file filepath = self.__class__._format_filepath(self.category, self.url_name) resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) From 6ed905275522272c2988211201913177d33bd9ad Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 3 Aug 2012 18:16:54 -0400 Subject: [PATCH 17/34] minor cleanups --- common/lib/xmodule/xmodule/abtest_module.py | 8 ++++++-- common/lib/xmodule/xmodule/course_module.py | 2 +- common/lib/xmodule/xmodule/modulestore/xml.py | 9 +++++++-- common/lib/xmodule/xmodule/x_module.py | 7 ++++--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/abtest_module.py b/common/lib/xmodule/xmodule/abtest_module.py index 8d6604b06b..9d4744e2c9 100644 --- a/common/lib/xmodule/xmodule/abtest_module.py +++ b/common/lib/xmodule/xmodule/abtest_module.py @@ -103,7 +103,9 @@ class ABTestDescriptor(RawDescriptor, XmlDescriptor): experiment = xml_object.get('experiment') if experiment is None: - raise InvalidDefinitionError("ABTests must specify an experiment. Not found in:\n{xml}".format(xml=etree.tostring(xml_object, pretty_print=True))) + raise InvalidDefinitionError( + "ABTests must specify an experiment. Not found in:\n{xml}" + .format(xml=etree.tostring(xml_object, pretty_print=True))) definition = { 'data': { @@ -127,7 +129,9 @@ class ABTestDescriptor(RawDescriptor, XmlDescriptor): definition['data']['group_content'][name] = child_content_urls definition['children'].extend(child_content_urls) - default_portion = 1 - sum(portion for (name, portion) in definition['data']['group_portions'].items()) + default_portion = 1 - sum( + portion for (name, portion) in definition['data']['group_portions'].items()) + if default_portion < 0: raise InvalidDefinitionError("ABTest portions must add up to less than or equal to 1") diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 7f4c204f45..40294fa786 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -37,7 +37,7 @@ class CourseDescriptor(SequenceDescriptor): def has_started(self): return time.gmtime() > self.start - + @property def grader(self): return self.__grading_policy['GRADER'] diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py index 46fcf19469..f9093f355a 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml.py +++ b/common/lib/xmodule/xmodule/modulestore/xml.py @@ -188,21 +188,26 @@ class XMLModuleStore(ModuleStoreBase): course_file = StringIO(clean_out_mako_templating(course_file.read())) course_data = etree.parse(course_file).getroot() + org = course_data.get('org') if org is None: - log.error("No 'org' attribute set for course in {dir}. " + msg = ("No 'org' attribute set for course in {dir}. " "Using default 'edx'".format(dir=course_dir)) + log.error(msg) + tracker(msg) org = 'edx' course = course_data.get('course') if course is None: - log.error("No 'course' attribute set for course in {dir}." + msg = ("No 'course' attribute set for course in {dir}." " Using default '{default}'".format( dir=course_dir, default=course_dir )) + log.error(msg) + tracker(msg) course = course_dir system = ImportSystem(self, org, course, course_dir, tracker) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 9b005d1dae..818e0cd8fd 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -6,6 +6,7 @@ from fs.errors import ResourceNotFoundError from functools import partial from lxml import etree from lxml.etree import XMLSyntaxError +from pprint import pprint from xmodule.modulestore import Location from xmodule.errortracker import exc_info_to_str @@ -550,9 +551,9 @@ class XModuleDescriptor(Plugin, HTMLSnippet): if not eq: for attr in self.equality_attributes: - print(getattr(self, attr, None), - getattr(other, attr, None), - getattr(self, attr, None) == getattr(other, attr, None)) + pprint((getattr(self, attr, None), + getattr(other, attr, None), + getattr(self, attr, None) == getattr(other, attr, None))) return eq From b091dcabe0f682695b8cbcf6030051c4cee0c8c6 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 3 Aug 2012 18:20:06 -0400 Subject: [PATCH 18/34] metadata and file format cleanups * course.xml is special--has org and course attributes in addition to url_name * strip data_dir from metadata on export * more asserts * work on roundtrip import-export test --- common/lib/xmodule/xmodule/course_module.py | 1 - .../lib/xmodule/xmodule/tests/test_export.py | 68 ++++++++++++++++--- .../lib/xmodule/xmodule/tests/test_import.py | 27 ++++++-- common/lib/xmodule/xmodule/xml_module.py | 55 ++++++++++++--- 4 files changed, 124 insertions(+), 27 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 40294fa786..41ed5ab89a 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -13,7 +13,6 @@ log = logging.getLogger(__name__) class CourseDescriptor(SequenceDescriptor): module_class = SequenceModule - metadata_attributes = SequenceDescriptor.metadata_attributes + ('org', 'course') def __init__(self, system, definition=None, **kwargs): super(CourseDescriptor, self).__init__(system, definition, **kwargs) diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py index eacf8352be..e1e48cc796 100644 --- a/common/lib/xmodule/xmodule/tests/test_export.py +++ b/common/lib/xmodule/xmodule/tests/test_export.py @@ -1,24 +1,72 @@ -from xmodule.modulestore.xml import XMLModuleStore -from nose.tools import assert_equals -from nose import SkipTest -from tempfile import mkdtemp from fs.osfs import OSFS +from nose.tools import assert_equals, assert_true +from nose import SkipTest +from path import path +from tempfile import mkdtemp + +from xmodule.modulestore.xml import XMLModuleStore + +# from ~/mitx_all/mitx/common/lib/xmodule/xmodule/tests/ +# to ~/mitx_all/mitx/common/test +TEST_DIR = path(__file__).abspath().dirname() +for i in range(4): + TEST_DIR = TEST_DIR.dirname() +TEST_DIR = TEST_DIR / 'test' + +DATA_DIR = TEST_DIR / 'data' -def check_export_roundtrip(data_dir): +def strip_metadata(descriptor, key): + """ + HACK: data_dir metadata tags break equality because they aren't real metadata + remove them. + + Recursively strips same tag from all children. + """ + print "strip {key} from {desc}".format(key=key, desc=descriptor.location.url()) + descriptor.metadata.pop(key, None) + for d in descriptor.get_children(): + strip_metadata(d, key) + +def check_gone(descriptor, key): + '''Make sure that the metadata of this descriptor or any + descendants does not include key''' + assert_true(key not in descriptor.metadata) + for d in descriptor.get_children(): + check_gone(d, key) + +def check_export_roundtrip(data_dir, course_dir): print "Starting import" - initial_import = XMLModuleStore('org', 'course', data_dir, eager=True) - initial_course = initial_import.course + initial_import = XMLModuleStore(data_dir, eager=True, course_dirs=[course_dir]) + + courses = initial_import.get_courses() + assert_equals(len(courses), 1) + initial_course = courses[0] print "Starting export" export_dir = mkdtemp() + print "export_dir: {0}".format(export_dir) fs = OSFS(export_dir) - xml = initial_course.export_to_xml(fs) - with fs.open('course.xml', 'w') as course_xml: + export_course_dir = 'export' + export_fs = fs.makeopendir(export_course_dir) + + xml = initial_course.export_to_xml(export_fs) + with export_fs.open('course.xml', 'w') as course_xml: course_xml.write(xml) print "Starting second import" - second_import = XMLModuleStore('org', 'course', export_dir, eager=True) + second_import = XMLModuleStore(export_dir, eager=True, + course_dirs=[export_course_dir]) + + courses2 = second_import.get_courses() + assert_equals(len(courses2), 1) + exported_course = courses2[0] + + print "Checking course equality" + strip_metadata(initial_course, 'data_dir') + strip_metadata(exported_course, 'data_dir') + + assert_equals(initial_course, exported_course) print "Checking key equality" assert_equals(initial_import.modules.keys(), second_import.modules.keys()) diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py index 45a142b8fe..d2080e1bf7 100644 --- a/common/lib/xmodule/xmodule/tests/test_import.py +++ b/common/lib/xmodule/xmodule/tests/test_import.py @@ -5,6 +5,7 @@ from fs.memoryfs import MemoryFS from lxml import etree from xmodule.x_module import XMLParsingSystem, XModuleDescriptor +from xmodule.xml_module import is_pointer_tag from xmodule.errortracker import make_error_tracker from xmodule.modulestore import Location from xmodule.modulestore.exceptions import ItemNotFoundError @@ -117,15 +118,19 @@ class ImportTestCase(unittest.TestCase): - inherited metadata doesn't leak to children. """ system = self.get_system() - v = "1 hour" + v = '1 hour' + org = 'foo' + course = 'bbhh' + url_name = 'test1' start_xml = ''' - + Two houses, ... - '''.format(grace=v) + '''.format(grace=v, org=org, course=course, url_name=url_name) descriptor = XModuleDescriptor.load_from_xml(start_xml, system, - 'org', 'course') + org, course) print descriptor, descriptor.metadata self.assertEqual(descriptor.metadata['graceperiod'], v) @@ -141,15 +146,25 @@ class ImportTestCase(unittest.TestCase): # Now export and check things resource_fs = MemoryFS() exported_xml = descriptor.export_to_xml(resource_fs) + + # Check that the exported xml is just a pointer print "Exported xml:", exported_xml + pointer = etree.fromstring(exported_xml) + self.assertTrue(is_pointer_tag(pointer)) + # but it's a special case course pointer + self.assertEqual(pointer.attrib['course'], course) + self.assertEqual(pointer.attrib['org'], org) # Does the course still have unicorns? - # hardcoded path to course - with resource_fs.open('course/test1.xml') as f: + with resource_fs.open('course/{url_name}.xml'.format(url_name=url_name)) as f: course_xml = etree.fromstring(f.read()) self.assertEqual(course_xml.attrib['unicorn'], 'purple') + # the course and org tags should be _only_ in the pointer + self.assertTrue('course' not in course_xml.attrib) + self.assertTrue('org' not in course_xml.attrib) + # did we successfully strip the url_name from the definition contents? self.assertTrue('url_name' not in course_xml.attrib) diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index 907fc302a3..cb0d05a83e 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -11,6 +11,29 @@ import sys log = logging.getLogger(__name__) + +def is_pointer_tag(xml_obj): + """ + Check if xml_obj is a pointer tag: . + No children, one attribute named url_name. + + Special case for course roots: the pointer is + + + xml_obj: an etree Element + + Returns a bool. + """ + if xml_obj.tag != "course": + expected_attr = set(['url_name']) + else: + expected_attr = set(['url_name', 'course', 'org']) + + actual_attr = set(xml_obj.attrib.keys()) + return len(xml_obj) == 0 and actual_attr == expected_attr + + + _AttrMapBase = namedtuple('_AttrMap', 'from_xml to_xml') class AttrMap(_AttrMapBase): @@ -41,16 +64,19 @@ class XmlDescriptor(XModuleDescriptor): # Note -- url_name isn't in this list because it's handled specially on # import and export. + + # TODO (vshnayder): Do we need a list of metadata we actually + # understand? And if we do, is this the place? + # Related: What's the right behavior for clean_metadata? metadata_attributes = ('format', 'graceperiod', 'showanswer', 'rerandomize', 'start', 'due', 'graded', 'display_name', 'url_name', 'hide_from_toc', 'ispublic', # if True, then course is listed for all users; see # VS[compat] Remove once unused. 'name', 'slug') - # VS[compat] -- remove once everything is in the CMS - # We don't want url_name in the metadata--it's in the location, so avoid - # confusion and duplication. - metadata_to_strip = ('url_name', ) + metadata_to_strip = ('data_dir', + # VS[compat] -- remove the below attrs once everything is in the CMS + 'course', 'org', 'url_name') # A dictionary mapping xml attribute names AttrMaps that describe how # to import and export them @@ -130,6 +156,8 @@ class XmlDescriptor(XModuleDescriptor): Subclasses should not need to override this except in special cases (e.g. html module)''' + # VS[compat] -- the filename tag should go away once everything is + # converted. (note: make sure html files still work once this goes away) filename = xml_object.get('filename') if filename is None: definition_xml = copy.deepcopy(xml_object) @@ -198,13 +226,13 @@ class XmlDescriptor(XModuleDescriptor): url identifiers """ xml_object = etree.fromstring(xml_data) - # VS[compat] -- just have the url_name lookup once translation is done + # VS[compat] -- just have the url_name lookup, once translation is done url_name = xml_object.get('url_name', xml_object.get('slug')) location = Location('i4x', org, course, xml_object.tag, url_name) # VS[compat] -- detect new-style each-in-a-file mode - if len(xml_object.attrib.keys()) == 1 and len(xml_object) == 0: - # new style: this is just a pointer. + if is_pointer_tag(xml_object): + # new style: # read the actual defition file--named using url_name filepath = cls._format_filepath(xml_object.tag, url_name) definition_xml = cls.load_file(filepath, system.resources_fs, location) @@ -258,12 +286,13 @@ class XmlDescriptor(XModuleDescriptor): # Add the non-inherited metadata for attr in self.own_metadata: - xml_object.set(attr, val_for_xml(attr)) + # don't want e.g. data_dir + if attr not in self.metadata_to_strip: + xml_object.set(attr, val_for_xml(attr)) - # Write the actual contents to a file + # Write the definition to a file filepath = self.__class__._format_filepath(self.category, self.url_name) resource_fs.makedir(os.path.dirname(filepath), allow_recreate=True) - with resource_fs.open(filepath, 'w') as file: file.write(etree.tostring(xml_object, pretty_print=True)) @@ -271,6 +300,12 @@ class XmlDescriptor(XModuleDescriptor): record_object = etree.Element(self.category) record_object.set('url_name', self.url_name) + # Special case for course pointers: + if self.category == 'course': + # add org and course attributes on the pointer tag + record_object.set('org', self.location.org) + record_object.set('course', self.location.course) + return etree.tostring(record_object, pretty_print=True) def definition_to_xml(self, resource_fs): From e6e290f525123cadcffd3d89660571eba47f0f5f Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 3 Aug 2012 19:03:19 -0400 Subject: [PATCH 19/34] Make initial import-export tests pass. TODO: * need unique slugs for errors so they don't overwrite each other on export. - try to preserve origin slug. If not possible, generate random one. * figure out what metadata to strip. e.g. ({'data': '

    Finger Exercise 1...'}, {'data': '

    Finger Exercise 1...'}, False) - where did points and type come from? Do we want them there? * separate broken and non-broken test courses --- common/lib/xmodule/xmodule/html_module.py | 11 +- .../lib/xmodule/xmodule/tests/test_export.py | 114 ++++++++++-------- .../lib/xmodule/xmodule/tests/test_import.py | 2 - common/lib/xmodule/xmodule/xml_module.py | 2 +- common/test/data/simple/course.xml | 2 +- .../{problems => problem}/L1_Problem_1.xml | 0 .../{problems => problem}/ps01-simple.xml | 0 7 files changed, 72 insertions(+), 59 deletions(-) rename common/test/data/simple/{problems => problem}/L1_Problem_1.xml (100%) rename common/test/data/simple/{problems => problem}/ps01-simple.xml (100%) diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index de45af081a..89dbfb5fc0 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -36,7 +36,6 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor): # are being edited in the cms @classmethod def backcompat_paths(cls, path): - origpath = path if path.endswith('.html.xml'): path = path[:-9] + '.html' #backcompat--look for html instead of xml candidates = [] @@ -45,9 +44,11 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor): _, _, path = path.partition(os.sep) # also look for .html versions instead of .xml - if origpath.endswith('.xml'): - candidates.append(origpath[:-4] + '.html') - return candidates + nc = [] + for candidate in candidates: + if candidate.endswith('.xml'): + nc.append(candidate[:-4] + '.html') + return candidates + nc # NOTE: html descriptors are special. We do not want to parse and # export them ourselves, because that can break things (e.g. lxml @@ -80,7 +81,7 @@ class HtmlDescriptor(XmlDescriptor, EditingDescriptor): # online and has imported all current (fall 2012) courses from xml if not system.resources_fs.exists(filepath): candidates = cls.backcompat_paths(filepath) - #log.debug("candidates = {0}".format(candidates)) + log.debug("candidates = {0}".format(candidates)) for candidate in candidates: if system.resources_fs.exists(candidate): filepath = candidate diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py index e1e48cc796..7358d89dde 100644 --- a/common/lib/xmodule/xmodule/tests/test_export.py +++ b/common/lib/xmodule/xmodule/tests/test_export.py @@ -1,6 +1,7 @@ +import unittest + from fs.osfs import OSFS from nose.tools import assert_equals, assert_true -from nose import SkipTest from path import path from tempfile import mkdtemp @@ -18,10 +19,7 @@ DATA_DIR = TEST_DIR / 'data' def strip_metadata(descriptor, key): """ - HACK: data_dir metadata tags break equality because they aren't real metadata - remove them. - - Recursively strips same tag from all children. + Recursively strips tag from all children. """ print "strip {key} from {desc}".format(key=key, desc=descriptor.location.url()) descriptor.metadata.pop(key, None) @@ -35,50 +33,66 @@ def check_gone(descriptor, key): for d in descriptor.get_children(): check_gone(d, key) -def check_export_roundtrip(data_dir, course_dir): - print "Starting import" - initial_import = XMLModuleStore(data_dir, eager=True, course_dirs=[course_dir]) - - courses = initial_import.get_courses() - assert_equals(len(courses), 1) - initial_course = courses[0] - - print "Starting export" - export_dir = mkdtemp() - print "export_dir: {0}".format(export_dir) - fs = OSFS(export_dir) - export_course_dir = 'export' - export_fs = fs.makeopendir(export_course_dir) - - xml = initial_course.export_to_xml(export_fs) - with export_fs.open('course.xml', 'w') as course_xml: - course_xml.write(xml) - - print "Starting second import" - second_import = XMLModuleStore(export_dir, eager=True, - course_dirs=[export_course_dir]) - - courses2 = second_import.get_courses() - assert_equals(len(courses2), 1) - exported_course = courses2[0] - - print "Checking course equality" - strip_metadata(initial_course, 'data_dir') - strip_metadata(exported_course, 'data_dir') - - assert_equals(initial_course, exported_course) - - print "Checking key equality" - assert_equals(initial_import.modules.keys(), second_import.modules.keys()) - - print "Checking module equality" - for location in initial_import.modules.keys(): - print "Checking", location - assert_equals(initial_import.modules[location], second_import.modules[location]) -def test_toy_roundtrip(): - dir = "" - # TODO: add paths and make this run. - raise SkipTest() - check_export_roundtrip(dir) +class RoundTripTestCase(unittest.TestCase): + '''Check that our test courses roundtrip properly''' + def check_export_roundtrip(self, data_dir, course_dir): + print "Starting import" + initial_import = XMLModuleStore(data_dir, eager=True, course_dirs=[course_dir]) + + courses = initial_import.get_courses() + self.assertEquals(len(courses), 1) + initial_course = courses[0] + + print "Starting export" + export_dir = mkdtemp() + print "export_dir: {0}".format(export_dir) + fs = OSFS(export_dir) + export_course_dir = 'export' + export_fs = fs.makeopendir(export_course_dir) + + xml = initial_course.export_to_xml(export_fs) + with export_fs.open('course.xml', 'w') as course_xml: + course_xml.write(xml) + + print "Starting second import" + second_import = XMLModuleStore(export_dir, eager=True, + course_dirs=[export_course_dir]) + + courses2 = second_import.get_courses() + self.assertEquals(len(courses2), 1) + exported_course = courses2[0] + + print "Checking course equality" + # HACK: data_dir metadata tags break equality because they + # aren't real metadata, and depend on paths. Remove them. + strip_metadata(initial_course, 'data_dir') + strip_metadata(exported_course, 'data_dir') + + self.assertEquals(initial_course, exported_course) + + print "Checking key equality" + self.assertEquals(sorted(initial_import.modules.keys()), + sorted(second_import.modules.keys())) + + print "Checking module equality" + for location in initial_import.modules.keys(): + print "Checking", location + if location.category == 'html': + print ("Skipping html modules--they can't import in" + " final form without writing files...") + continue + self.assertEquals(initial_import.modules[location], + second_import.modules[location]) + + + def setUp(self): + self.maxDiff = None + + def test_toy_roundtrip(self): + self.check_export_roundtrip(DATA_DIR, "toy") + + + def test_full_roundtrip(self): + self.check_export_roundtrip(DATA_DIR, "full") diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py index d2080e1bf7..af7464cfdc 100644 --- a/common/lib/xmodule/xmodule/tests/test_import.py +++ b/common/lib/xmodule/xmodule/tests/test_import.py @@ -51,8 +51,6 @@ class DummySystem(XMLParsingSystem): class ImportTestCase(unittest.TestCase): '''Make sure module imports work properly, including for malformed inputs''' - - @staticmethod def get_system(): '''Get a dummy system''' diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index cb0d05a83e..1318270def 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -76,7 +76,7 @@ class XmlDescriptor(XModuleDescriptor): metadata_to_strip = ('data_dir', # VS[compat] -- remove the below attrs once everything is in the CMS - 'course', 'org', 'url_name') + 'course', 'org', 'url_name', 'filename') # A dictionary mapping xml attribute names AttrMaps that describe how # to import and export them diff --git a/common/test/data/simple/course.xml b/common/test/data/simple/course.xml index b92158cdb7..0ecc153724 100644 --- a/common/test/data/simple/course.xml +++ b/common/test/data/simple/course.xml @@ -2,7 +2,7 @@

-
+ -
-

Apply to change your name

-
-
-
-

To uphold the credibility of edX certificates, name changes must go through an approval process. A member of the course staff will review your request, and if approved, update your information. Please allow up to a week for your request to be processed. Thank you.

-
    -
  • - - -
  • -
  • - - -
  • -
  • - -
  • -
-
-
+ -
-

Change e-mail

+ -
  • - - -
  • -
  • -

    We will send a confirmation to both ${email} and your new e-mail as part of the process.

    - -
  • - + -
    -

    Deactivate edX Account

    -

    Once you deactivate you’re MITx account you will no longer recieve updates and new class announcements from MITx.

    -

    If you’d like to still get updates and new class announcements you can just unenroll and keep your account active.

    + diff --git a/lms/templates/simplewiki/simplewiki_edit.html b/lms/templates/simplewiki/simplewiki_edit.html index 7618dd629c..856dfb9cc8 100644 --- a/lms/templates/simplewiki/simplewiki_edit.html +++ b/lms/templates/simplewiki/simplewiki_edit.html @@ -65,12 +65,10 @@ ${"Edit " + wiki_title + " - " if wiki_title is not UNDEFINED else ""}MITx 6.002
    ${wiki_form} %if create_article: - + %else: - %endif - <%include file="simplewiki_instructions.html"/> diff --git a/lms/templates/simplewiki/simplewiki_searchresults.html b/lms/templates/simplewiki/simplewiki_searchresults.html index d94cbf9c25..e64a01ae62 100644 --- a/lms/templates/simplewiki/simplewiki_searchresults.html +++ b/lms/templates/simplewiki/simplewiki_searchresults.html @@ -26,7 +26,7 @@ Displaying all articles
  • ${article.title} ${'(Deleted)' if article_deleted else ''}

  • %endfor -%if not wiki_search_results: +%if not wiki_search_results: No articles matching ${wiki_search_query if wiki_search_query is not UNDEFINED else ""} ! %endif From 9cba2232d5637bbf59bc5015fcd402e70a57fa94 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Tue, 7 Aug 2012 13:31:29 -0400 Subject: [PATCH 34/34] Run all tests all the way through before quitting on errors --- rakefile | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/rakefile b/rakefile index 01491ce981..caf0d58f2f 100644 --- a/rakefile +++ b/rakefile @@ -83,13 +83,20 @@ end task :pylint => "pylint_#{system}" end +$failed_tests = 0 -def run_tests(system, report_dir) +def run_tests(system, report_dir, stop_on_failure=true) ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml") ENV['NOSE_COVER_HTML_DIR'] = File.join(report_dir, "cover") - sh(django_admin(system, :test, 'test', *Dir["#{system}/djangoapps/*"].each)) + sh(django_admin(system, :test, 'test', *Dir["#{system}/djangoapps/*"].each)) do |ok, res| + if !ok and stop_on_failure + abort "Test failed!" + end + $failed_tests += 1 unless ok + end end +TEST_TASKS = [] [:lms, :cms].each do |system| report_dir = File.join(REPORT_DIR, system.to_s) @@ -97,15 +104,16 @@ end # Per System tasks desc "Run all django tests on our djangoapps for the #{system}" - task "test_#{system}" => ["clean_test_files", "#{system}:collectstatic:test", "fasttest_#{system}"] + task "test_#{system}", [:stop_on_failure] => ["clean_test_files", "#{system}:collectstatic:test", "fasttest_#{system}"] # Have a way to run the tests without running collectstatic -- useful when debugging without # messing with static files. - task "fasttest_#{system}" => [report_dir, :predjango] do - run_tests(system, report_dir) + task "fasttest_#{system}", [:stop_on_failure] => [report_dir, :predjango] do |t, args| + args.with_defaults(:stop_on_failure => 'true') + run_tests(system, report_dir, args.stop_on_failure) end - task :test => "test_#{system}" + TEST_TASKS << "test_#{system}" desc <<-desc Start the #{system} locally with the specified environment (defaults to dev). @@ -142,7 +150,17 @@ Dir["common/lib/*"].each do |lib| ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml") sh("nosetests #{lib} --cover-erase --with-xunit --with-xcoverage --cover-html --cover-inclusive --cover-package #{File.basename(lib)} --cover-html-dir #{File.join(report_dir, "cover")}") end - task :test => task_name + TEST_TASKS << task_name +end + +task :test do + TEST_TASKS.each do |task| + Rake::Task[task].invoke(false) + end + + if $failed_tests > 0 + abort "Tests failed!" + end end task :runserver => :lms