From 630607d34554aef03f4e74adbdf26f0dd3b01496 Mon Sep 17 00:00:00 2001 From: ichuang Date: Tue, 5 Feb 2013 00:21:04 +0000 Subject: [PATCH 001/483] fix forum management commands (assign_default_role moved); add reload_forum_users --- .../commands/assign_roles_for_course.py | 3 +- .../commands/create_roles_for_existing.py | 3 +- .../management/commands/reload_forum_users.py | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 lms/djangoapps/django_comment_client/management/commands/reload_forum_users.py diff --git a/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py b/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py index 82f2290bc7..64378108b6 100644 --- a/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py +++ b/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py @@ -6,7 +6,8 @@ Enrollments. """ from django.core.management.base import BaseCommand, CommandError -from student.models import CourseEnrollment, assign_default_role +from student.models import CourseEnrollment +from django_comment_client.models import assign_default_role class Command(BaseCommand): args = 'course_id' diff --git a/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py b/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py index d1244a6690..3ec2d0646e 100644 --- a/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py +++ b/lms/djangoapps/django_comment_client/management/commands/create_roles_for_existing.py @@ -6,7 +6,8 @@ Enrollments. """ from django.core.management.base import BaseCommand, CommandError -from student.models import CourseEnrollment, assign_default_role +from student.models import CourseEnrollment +from django_comment_client.models import assign_default_role class Command(BaseCommand): args = 'course_id' diff --git a/lms/djangoapps/django_comment_client/management/commands/reload_forum_users.py b/lms/djangoapps/django_comment_client/management/commands/reload_forum_users.py new file mode 100644 index 0000000000..5e7e268270 --- /dev/null +++ b/lms/djangoapps/django_comment_client/management/commands/reload_forum_users.py @@ -0,0 +1,29 @@ +""" +Reload forum (comment client) users from existing users. +""" +from django.core.management.base import BaseCommand, CommandError + +from django.contrib.auth.models import User +import comment_client as cc + +class Command(BaseCommand): + help = 'Reload forum (comment client) users from existing users' + + def adduser(self,user): + print user + try: + cc_user = cc.User.from_django_user(user) + cc_user.save() + except Exception as err: + print "update user info to discussion failed for user with id: %s" % user + + def handle(self, *args, **options): + if len(args) != 0: + uset = [User.objects.get(username=x) for x in args] + else: + uset = User.objects.all() + + for user in uset: + self.adduser(user) + + \ No newline at end of file From 66c91b704a2a50ff7c4ae581de2b451f3b809451 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 15 Feb 2013 17:16:05 -0500 Subject: [PATCH 002/483] init work. Export the 'draft' store under a distinct folder in the export. TBD: do the import from that directory as well. --- cms/djangoapps/contentstore/views.py | 2 +- .../lib/xmodule/xmodule/modulestore/draft.py | 4 ++++ .../xmodule/modulestore/xml_exporter.py | 20 ++++++++++++++++++- .../xmodule/modulestore/xml_importer.py | 6 ++++++ common/lib/xmodule/xmodule/xml_module.py | 6 +++--- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 87a2943773..af62276ec4 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1426,7 +1426,7 @@ def generate_export_course(request, org, course, name): logging.debug('root = {0}'.format(root_dir)) - export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name) + export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore()) #filename = root_dir / name + '.tar.gz' logging.debug('tar file being generated at {0}'.format(export_file.name)) diff --git a/common/lib/xmodule/xmodule/modulestore/draft.py b/common/lib/xmodule/xmodule/modulestore/draft.py index 81f4da2780..6124d240a7 100644 --- a/common/lib/xmodule/xmodule/modulestore/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/draft.py @@ -183,6 +183,10 @@ class DraftModuleStore(ModuleStoreBase): metadata.update(draft.metadata) metadata['published_date'] = tuple(datetime.utcnow().timetuple()) metadata['published_by'] = published_by_id + + if 'is_draft' in metadata: + del metadata['is_draft'] + super(DraftModuleStore, self).update_item(location, draft.definition.get('data', {})) super(DraftModuleStore, self).update_children(location, draft.definition.get('children', [])) super(DraftModuleStore, self).update_metadata(location, metadata) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py index 55844116c6..e8d3fb0f82 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_exporter.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_exporter.py @@ -5,7 +5,7 @@ from fs.osfs import OSFS from json import dumps -def export_to_xml(modulestore, contentstore, course_location, root_dir, course_dir): +def export_to_xml(modulestore, contentstore, course_location, root_dir, course_dir, draft_modulestore = None): course = modulestore.get_item(course_location) @@ -41,6 +41,24 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d policy = {'course/' + course.location.name: course.metadata} course_policy.write(dumps(policy)) + # export everything from the draft store, unfortunately this will create lots of duplicates + if draft_modulestore is not None: + draft_course = draft_modulestore.get_item(course_location) + draft_course_dir = export_fs.makeopendir('drafts') + xml = draft_course.export_to_xml(draft_course_dir) + with draft_course_dir.open('course.xml', 'w') as course_xml: + course_xml.write(xml) + + ''' + draft_items = modulestore.get_items([None, None, None, 'vertical', None, 'draft']) + logging.debug('draft_items = {0}'.format(draft_items)) + if len(draft_items) > 0: + + for draft_item in draft_items: + draft_item.export_to_xml(draft_items_dir) + #with draft_items_dir.open(draft_item.location.name + '.xml', 'w'): + ''' + def export_extra_content(export_fs, modulestore, course_location, category_type, dirname, file_suffix=''): query_loc = Location('i4x', course_location.org, course_location.course, category_type, None) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 0b77900ae9..9fcd75d6f4 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -220,9 +220,15 @@ def import_from_xml(store, data_dir, course_dirs=None, # inherited metadata everywhere. store.update_metadata(module.location, dict(module.own_metadata)) + # now import any 'draft' items + import_course_draft(store, course_data_path, target_location_namespace) + return module_store, course_items +def import_course_draft(store, course_data_path, target_location_namespace): + pass + def remap_namespace(module, target_location_namespace): if target_location_namespace is None: return module diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index 64c3aabbcc..9081206491 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -105,8 +105,7 @@ class XmlDescriptor(XModuleDescriptor): 'name', 'slug') metadata_to_strip = ('data_dir', - # cdodge: @TODO: We need to figure out a way to export out 'tabs' and 'grading_policy' which is on the course - 'tabs', 'grading_policy', 'is_draft', 'published_by', 'published_date', + 'tabs', 'grading_policy', 'published_by', 'published_date', 'discussion_blackouts', 'testcenter_info', # VS[compat] -- remove the below attrs once everything is in the CMS 'course', 'org', 'url_name', 'filename') @@ -129,7 +128,8 @@ class XmlDescriptor(XModuleDescriptor): 'hide_progress_tab': bool_map, 'allow_anonymous': bool_map, 'allow_anonymous_to_peers': bool_map, - 'weight': int_map + 'weight': int_map, + 'is_draft': bool_map } From 22639adc639470da940b7ed7720a9d0bb1668f38 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 21 Feb 2013 14:31:24 -0500 Subject: [PATCH 003/483] studio - alerts WIP --- cms/static/sass/_alerts.scss | 323 ++++++++++++++++++++++++++------ cms/static/sass/_header.scss | 2 +- cms/static/sass/_variables.scss | 44 ++++- cms/templates/unit.html | 28 ++- 4 files changed, 338 insertions(+), 59 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 11d2e4fe3a..4a20980a57 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -1,65 +1,280 @@ -.alert { - padding: 15px 20px; - margin-bottom: 30px; - border-radius: 3px; - border: 1px solid #edbd3c; - border-radius: 3px; - background: #fbf6e1; - // background: #edbd3c; - font-size: 14px; - @include clearfix; +// studio alerts and notifications +// ==================== - .alert-message { - float: left; - margin-top: 4px; - } +// notifications +.wrapper-notification { + @include clearfix(); + @include box-sizing(border-box); + @include transition (bottom 2.0s ease-in-out 5s, opacity 2.0s ease-in-out 5s); + @include box-shadow(0 -1px 2px rgba(0,0,0,0.1)); + position: fixed; + bottom: -100px; + z-index: 1000; + width: 100%; + overflow: hidden; + opacity: 0; + border-top: 1px solid $gray-d1; + padding: $baseline ($baseline*2); + background: $white; - strong { - font-weight: 700; - } + &.is-shown { + bottom: 0; + opacity: 1.0; + } - .alert-action { - float: right; + &.wrapper-notification-warning { + border-color: shade($yellow, 25%); + background: tint($yellow, 25%); + } - &.secondary { - @include orange-button; - } - } + &.wrapper-notification-error { + border-color: shade($red, 50%); + background: tint($red, 20%); + color: $white; + } + + &.wrapper-notification-confirm { + border-color: shade($green, 30%); + background: tint($green, 40%); + color: shade($green, 30%); + } } -body.error { - background: $darkGrey; - color: #3c3c3c; +.notification { + @include box-sizing(border-box); + margin: 0 auto; + width: flex-grid(12); + max-width: $fg-max-width; + min-width: $fg-min-width; - .primary-header { - display: none; - } + .copy { + float: left; + width: flex-grid(9, 12); + margin-right: flex-gutter(); + margin-top: 5px; + font-size: 14px; - .error-prompt { - width: 700px; - margin: 150px auto; - padding: 60px 50px 90px; - border-radius: 3px; - background: #fff; - text-align: center; - } + .icon { + display: inline-block; + vertical-align: top; + margin-right: 5px; + font-size: 20px; + } - h1 { - float: none; - margin: 0; - font-size: 60px; - font-weight: 300; - color: #3c3c3c; - } + p { + width: flex-grid(8, 9); + display: inline-block; + vertical-align: top; + } + } - .description { - margin-bottom: 50px; - font-size: 21px; - } + .actions { + float: right; + width: flex-grid(3, 12); + margin-top: ($baseline/2); + text-align: right; - .back-button { - @include blue-button; - padding: 14px 40px 18px; - font-size: 18px; - } -} \ No newline at end of file + li { + display: inline-block; + vertical-align: middle; + margin-right: 10px; + + &:last-child { + margin-right: 0; + } + } + + .save-button { + @include blue-button; + } + + .cancel-button { + @include white-button; + } + } + + strong { + font-weight: 700; + } +} + +// ==================== + +// alerts +.wrapper-alert { + @include clearfix(); + @include box-sizing(border-box); + @include transition (opacity 2.0s ease-in-out 5s); + @include box-shadow(0 1px 2px rgba(0,0,0,0.2)); + position: relative; + top: -($baseline*1.5); + z-index: 100; + overflow: hidden; + width: 100%; + opacity: 0; + border-bottom: 2px solid $blue-d2; + padding: $baseline ($baseline*2); + background: $blue; + + &.is-shown { + bottom: 0; + opacity: 1.0; + } + + &.wrapper-alert-warning { + background: $orange-s1; + border-color: $orange-d1; + } + + &.wrapper-alert-error { + border-color: shade($red, 50%); + color: $white; + } + + &.wrapper-alert-confirm { + border-color: shade($green, 30%); + } + + &.wrapper-alert-inform { + border-color: $blue-d1; + } +} + +.alert { + @include font-size(14); + @include box-sizing(border-box); + margin: 0 auto; + width: flex-grid(12); + max-width: $fg-max-width; + min-width: $fg-min-width; + color: $white; + + strong { + font-weight: 700; + } + + .copy { + @include font-size(14); + + .icon { + @include font-size(24); + @include border-radius(50px); + display: inline-block; + vertical-align: top; + margin-right: ($baseline/2); + padding: ($baseline/4) ($baseline/2) ($baseline/10) ($baseline/2); + } + + p { + width: flex-grid(8, 9); + display: inline-block; + vertical-align: top; + } + } + + &.has-actions { + + .copy { + float: left; + width: flex-grid(8, 12); + margin-right: flex-gutter(); + } + + .actions { + float: right; + width: flex-grid(4, 12); + margin-top: ($baseline/2); + text-align: right; + + li { + display: inline-block; + vertical-align: middle; + margin-right: ($baseline/2); + + &:last-child { + margin-right: 0; + } + } + } + } + + // alert types + &.warning { + color: $gray-d2; + + .copy { + + .icon { + background: $gray-d2; + color: $orange-s1; + } + } + } +} + +// artifact styles +// .alert { +// padding: 15px 20px; +// margin-bottom: 30px; +// border-radius: 3px; +// border: 1px solid #edbd3c; +// border-radius: 3px; +// background: #fbf6e1; +// // background: #edbd3c; +// font-size: 14px; +// @include clearfix; + +// .alert-message { +// float: left; +// margin-top: 4px; +// } + +// strong { +// font-weight: 700; +// } + +// .alert-action { +// float: right; + +// &.secondary { +// @include orange-button; +// } +// } +// } + +// body.error { +// background: $darkGrey; +// color: #3c3c3c; + +// .primary-header { +// display: none; +// } + +// .error-prompt { +// width: 700px; +// margin: 150px auto; +// padding: 60px 50px 90px; +// border-radius: 3px; +// background: #fff; +// text-align: center; +// } + +// h1 { +// float: none; +// margin: 0; +// font-size: 60px; +// font-weight: 300; +// color: #3c3c3c; +// } + +// .description { +// margin-bottom: 50px; +// font-size: 21px; +// } + +// .back-button { +// @include blue-button; +// padding: 14px 40px 18px; +// font-size: 18px; +// } +// } \ No newline at end of file diff --git a/cms/static/sass/_header.scss b/cms/static/sass/_header.scss index ca1092f44b..3115711506 100644 --- a/cms/static/sass/_header.scss +++ b/cms/static/sass/_header.scss @@ -10,7 +10,7 @@ height: 76px; position: relative; width: 100%; - z-index: 10; + z-index: 1000; a { color: $baseFontColor; diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index 4d8e26b2f9..eb2bd29ebd 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -39,6 +39,12 @@ $blue-d1: shade($blue,20%); $blue-d2: shade($blue,40%); $blue-d3: shade($blue,60%); $blue-d4: shade($blue,80%); +$blue-s1: saturate($blue,15%); +$blue-s2: saturate($blue,30%); +$blue-s3: saturate($blue,45%); +$blue-u1: desaturate($blue,15%); +$blue-u2: desaturate($blue,30%); +$blue-u3: desaturate($blue,45%); $pink: rgb(183, 37, 103); $pink-l1: tint($pink,20%); @@ -50,6 +56,12 @@ $pink-d1: shade($pink,20%); $pink-d2: shade($pink,40%); $pink-d3: shade($pink,60%); $pink-d4: shade($pink,80%); +$pink-s1: saturate($pink,15%); +$pink-s2: saturate($pink,30%); +$pink-s3: saturate($pink,45%); +$pink-u1: desaturate($pink,15%); +$pink-u2: desaturate($pink,30%); +$pink-u3: desaturate($pink,45%); $green: rgb(37, 184, 90); $green-l1: tint($green,20%); @@ -61,8 +73,14 @@ $green-d1: shade($green,20%); $green-d2: shade($green,40%); $green-d3: shade($green,60%); $green-d4: shade($green,80%); +$green-s1: saturate($green,15%); +$green-s2: saturate($green,30%); +$green-s3: saturate($green,45%); +$green-u1: desaturate($green,15%); +$green-u2: desaturate($green,30%); +$green-u3: desaturate($green,45%); -$yellow: rgb(231, 214, 143); +$yellow: rgb(237, 189, 60); $yellow-l1: tint($yellow,20%); $yellow-l2: tint($yellow,40%); $yellow-l3: tint($yellow,60%); @@ -72,9 +90,33 @@ $yellow-d1: shade($yellow,20%); $yellow-d2: shade($yellow,40%); $yellow-d3: shade($yellow,60%); $yellow-d4: shade($yellow,80%); +$yellow-s1: saturate($yellow,15%); +$yellow-s2: saturate($yellow,30%); +$yellow-s3: saturate($yellow,45%); +$yellow-u1: desaturate($yellow,15%); +$yellow-u2: desaturate($yellow,30%); +$yellow-u3: desaturate($yellow,45%); + +$orange: rgb(237, 189, 60); +$orange-l1: tint($orange,20%); +$orange-l2: tint($orange,40%); +$orange-l3: tint($orange,60%); +$orange-l4: tint($orange,80%); +$orange-l5: tint($orange,90%); +$orange-d1: shade($orange,20%); +$orange-d2: shade($orange,40%); +$orange-d3: shade($orange,60%); +$orange-d4: shade($orange,80%); +$orange-s1: saturate($orange,15%); +$orange-s2: saturate($orange,30%); +$orange-s3: saturate($orange,45%); +$orange-u1: desaturate($orange,15%); +$orange-u2: desaturate($orange,30%); +$orange-u3: desaturate($orange,45%); $shadow: rgba(0,0,0,0.2); $shadow-l1: rgba(0,0,0,0.1); +$shadow-l2: rgba(0,0,0,0.05); $shadow-d1: rgba(0,0,0,0.4); // colors - inherited diff --git a/cms/templates/unit.html b/cms/templates/unit.html index c529f5863a..53be470a50 100644 --- a/cms/templates/unit.html +++ b/cms/templates/unit.html @@ -16,7 +16,6 @@ }); $(document).ready(function() { - $('body').addClass('js'); // tabs $('.tab-group').tabs(); @@ -32,9 +31,32 @@ <%block name="content"> + + +
+
+
+ +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+ + +
+
+
-
+

You are editing a draft. % if published_date: This unit was originally published on ${published_date}. @@ -144,7 +166,7 @@

-
+

This unit has been published. To make changes, you must edit a draft.

This is a draft of the published unit. To update the live version, you must replace it with this draft.

From d7bbfbc707299ec1f5db960ee892075b5bc29269 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 22 Feb 2013 13:52:24 -0500 Subject: [PATCH 004/483] studio - alerts: basic layout and general alert styling WIP --- cms/static/sass/_alerts.scss | 161 ++++++++++++++++++++++++-------- cms/static/sass/_base.scss | 45 +++++++++ cms/static/sass/_header.scss | 2 +- cms/static/sass/_variables.scss | 18 +++- cms/templates/overview.html | 129 +++++++++++++++++++++++++ cms/templates/unit.html | 23 ----- 6 files changed, 314 insertions(+), 64 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 4a20980a57..c1e64fb889 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -102,19 +102,19 @@ // alerts .wrapper-alert { - @include clearfix(); @include box-sizing(border-box); - @include transition (opacity 2.0s ease-in-out 5s); - @include box-shadow(0 1px 2px rgba(0,0,0,0.2)); + @include transition (opacity 2.0s ease-in-out 5s, border 0.5s ease-in-out); + @include box-shadow(0 1px 1px $white, inset 0 2px 5px $shadow-d1); position: relative; top: -($baseline*1.5); z-index: 100; overflow: hidden; width: 100%; opacity: 0; - border-bottom: 2px solid $blue-d2; + border-bottom: 4px solid $gray-l1; + border-top: 1px solid $black; padding: $baseline ($baseline*2); - background: $blue; + background: $gray-d3; &.is-shown { bottom: 0; @@ -122,27 +122,95 @@ } &.wrapper-alert-warning { - background: $orange-s1; - border-color: $orange-d1; + border-bottom-color: $orange; + + .icon-warning { + color: $orange; + } + + + &:hover { + border-bottom-color: $orange-s2; + + .icon-warning { + color: $orange-s2; + } + } } &.wrapper-alert-error { - border-color: shade($red, 50%); - color: $white; + border-bottom-color: $red; + + .icon-error { + color: $red; + } + + + &:hover { + border-bottom-color: $red-s2; + + .icon-error { + color: $red-s2; + } + } } - &.wrapper-alert-confirm { - border-color: shade($green, 30%); + &.wrapper-alert-confirmation { + border-bottom-color: $green; + + .icon-confirmation { + color: $green; + } + + + &:hover { + border-bottom-color: $green-s2; + + .icon-confirmation { + color: $green-s2; + } + } } - &.wrapper-alert-inform { - border-color: $blue-d1; + &.wrapper-alert-announcement { + border-bottom-color: $blue; + + .icon-announcement { + color: $blue; + } + + + &:hover { + border-bottom-color: $blue-s2; + + .icon-announcement { + color: $blue-s2; + } + } + } + + &.wrapper-alert-step-required { + border-bottom-color: $pink; + + .icon-step-required { + color: $pink; + } + + + &:hover { + border-bottom-color: $pink-s2; + + .icon-announcement { + color: $pink-s2; + } + } } } .alert { @include font-size(14); @include box-sizing(border-box); + @include clearfix(); margin: 0 auto; width: flex-grid(12); max-width: $fg-max-width; @@ -153,40 +221,47 @@ font-weight: 700; } + .icon, .copy { + float: left; + } + + .icon { + @include transition (color 0.5s ease-in-out); + @include font-size(28); + width: flex-grid(1, 12); + margin-right: flex-gutter(); + text-align: right; + } + .copy { @include font-size(14); + width: flex-grid(10, 12); + color: $gray-l2; - .icon { - @include font-size(24); - @include border-radius(50px); - display: inline-block; - vertical-align: top; - margin-right: ($baseline/2); - padding: ($baseline/4) ($baseline/2) ($baseline/10) ($baseline/2); - } - - p { - width: flex-grid(8, 9); - display: inline-block; - vertical-align: top; + .title { + margin-bottom: 0; + color: $white; } } &.has-actions { + .icon { + width: flex-grid(1, 12); + } + .copy { - float: left; width: flex-grid(8, 12); margin-right: flex-gutter(); } - .actions { - float: right; - width: flex-grid(4, 12); - margin-top: ($baseline/2); + .nav-actions { + width: flex-grid(3, 12); + float: right; + margin-top: ($baseline/2); text-align: right; - li { + .nav-item { display: inline-block; vertical-align: middle; margin-right: ($baseline/2); @@ -200,15 +275,23 @@ // alert types &.warning { - color: $gray-d2; + + } - .copy { + &.error { + + } + + &.confirmation { + + } + + &.announcement { + + } + + &.step-required { - .icon { - background: $gray-d2; - color: $orange-s1; - } - } } } diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 5d4bc7c773..62f72e8d63 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -50,6 +50,51 @@ h1 { // ==================== +// typography - basic +.title-1, .title-2, .title-3, .title-4, .title-5, .title-6 { + font-weight: 600; + color: $gray-d3; + margin: 0; + padding: 0; +} + +.title-1 { + @include font-size(32); + margin-bottom: ($baseline*1.5); +} + +.title-2 { + @include font-size(24); + margin-bottom: $baseline; +} + +.title-3 { + @include font-size(16); + margin-bottom: ($baseline/2); +} + +.title-4 { + @include font-size(14); + margin-bottom: $baseline; + font-weight: 500 +} + +.title-5 { + @include font-size(14); + color: $gray-l1; + margin-bottom: $baseline; + font-weight: 500 +} + +.title-6 { + @include font-size(14); + color: $gray-l2; + margin-bottom: $baseline; + font-weight: 500 +} + +// ==================== + // layout - basic page header .wrapper-mast { margin: 0; diff --git a/cms/static/sass/_header.scss b/cms/static/sass/_header.scss index 3115711506..53edbd3e9e 100644 --- a/cms/static/sass/_header.scss +++ b/cms/static/sass/_header.scss @@ -5,7 +5,7 @@ margin: 0 0 ($baseline*1.5) 0; padding: $baseline; border-bottom: 1px solid $gray; - @include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1)); + @include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.2)); background: $white; height: 76px; position: relative; diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index eb2bd29ebd..907d5cbdb2 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -63,6 +63,23 @@ $pink-u1: desaturate($pink,15%); $pink-u2: desaturate($pink,30%); $pink-u3: desaturate($pink,45%); +$red: rgb(178, 6, 16); +$red-l1: tint($red,20%); +$red-l2: tint($red,40%); +$red-l3: tint($red,60%); +$red-l4: tint($red,80%); +$red-l5: tint($red,90%); +$red-d1: shade($red,20%); +$red-d2: shade($red,40%); +$red-d3: shade($red,60%); +$red-d4: shade($red,80%); +$red-s1: saturate($red,15%); +$red-s2: saturate($red,30%); +$red-s3: saturate($red,45%); +$red-u1: desaturate($red,15%); +$red-u2: desaturate($red,30%); +$red-u3: desaturate($red,45%); + $green: rgb(37, 184, 90); $green-l1: tint($green,20%); $green-l2: tint($green,40%); @@ -123,7 +140,6 @@ $shadow-d1: rgba(0,0,0,0.4); $baseFontColor: #3c3c3c; $offBlack: #3c3c3c; $orange: #edbd3c; -$red: #b20610; $green: #108614; $lightGrey: #edf1f5; $mediumGrey: #b0b6c2; diff --git a/cms/templates/overview.html b/cms/templates/overview.html index 91a1107726..292246676b 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -106,6 +106,135 @@ <%block name="content"> + +
+
+ + +
+

You are editing a draft

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

Your changes have been saved

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

+
+
+
+ + +
+
+ + +
+

X Has been removed

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+
+ + +
+
+ + +
+

We're Sorry, there was a error with Studio

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+
+ + +
+
+ + +
+

There was an error in your submission

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

+
+ + +
+
+ + +
+
+ 📢 + +
+

Studio will be unavailable this weekend

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+ + +
+
+ + +
+
+ 📢 + +
+

Studio will be unavailable this weekend

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+
+ + +
+
+ + +
+

Your Studio account has been created, but needs to be activated

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+
+

Section Release Date

diff --git a/cms/templates/unit.html b/cms/templates/unit.html index 53be470a50..a95061233b 100644 --- a/cms/templates/unit.html +++ b/cms/templates/unit.html @@ -31,29 +31,6 @@ <%block name="content"> - - -
-
-
- -

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
- - -
-
-
From bb24a079a19939bfbf77202c3ae6d37055b4098c Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 22 Feb 2013 14:28:13 -0500 Subject: [PATCH 005/483] studio - alerts: initial pass at alert styling and markup complete --- cms/static/sass/_alerts.scss | 92 ++++++++++++-- cms/static/sass/_cms_mixins.scss | 204 ++++++++++++++++++------------- cms/templates/overview.html | 56 +++++++-- common/static/sass/_mixins.scss | 34 ++++++ 4 files changed, 283 insertions(+), 103 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index c1e64fb889..393539e90d 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -128,7 +128,6 @@ color: $orange; } - &:hover { border-bottom-color: $orange-s2; @@ -139,18 +138,17 @@ } &.wrapper-alert-error { - border-bottom-color: $red; + border-bottom-color: $red-l1; .icon-error { - color: $red; + color: $red-l1; } - &:hover { - border-bottom-color: $red-s2; + border-bottom-color: $red; .icon-error { - color: $red-s2; + color: $red; } } } @@ -162,7 +160,6 @@ color: $green; } - &:hover { border-bottom-color: $green-s2; @@ -179,7 +176,6 @@ color: $blue; } - &:hover { border-bottom-color: $blue-s2; @@ -196,7 +192,6 @@ color: $pink; } - &:hover { border-bottom-color: $pink-s2; @@ -251,12 +246,12 @@ } .copy { - width: flex-grid(8, 12); + width: flex-grid(7, 12); margin-right: flex-gutter(); } .nav-actions { - width: flex-grid(3, 12); + width: flex-grid(4, 12); float: right; margin-top: ($baseline/2); text-align: right; @@ -269,6 +264,21 @@ &:last-child { margin-right: 0; } + + .action-primary { + @include transition (opacity 0.25s ease-in-out); + @include font-size(13); + font-weight: 600; + opacity: 0.90; + + &:hover { + opacity: 1.0; + } + } + + .action-secondary { + @include font-size(13); + } } } } @@ -276,22 +286,82 @@ // alert types &.warning { + .action-primary { + @include orange-button; + border-color: $orange-d2; + } + + a { + color: $orange; + + &:hover { + color: $orange-s2; + } + } } &.error { + .action-primary { + @include red-button; + border-color: $red-d2; + } + + a { + color: $red-l1; + + &:hover { + color: $red; + } + } } &.confirmation { + .action-primary { + @include green-button; + border-color: $green-d2; + } + + a { + color: $green; + + &:hover { + color: $green-s2; + } + } } &.announcement { + .action-primary { + @include blue-button; + border-color: $blue-d2; + } + + a { + color: $blue; + + &:hover { + color: $blue-s2; + } + } } &.step-required { + .action-primary { + border-color: $pink-d2; + @include pink-button; + } + + a { + color: $pink; + + &:hover { + color: $pink-s1; + } + } } } diff --git a/cms/static/sass/_cms_mixins.scss b/cms/static/sass/_cms_mixins.scss index b8d9a8ae2e..bd28702895 100644 --- a/cms/static/sass/_cms_mixins.scss +++ b/cms/static/sass/_cms_mixins.scss @@ -1,26 +1,20 @@ -@mixin clearfix { - &:after { - content: ''; - display: block; - height: 0; - visibility: hidden; - clear: both; - } -} +// studio specific mixins +// ==================== +// buttons @mixin button { display: inline-block; - padding: 4px 20px 6px; - font-size: 14px; + padding: ($baseline/5) $baseline ($baseline/4); + @include font-size(14); font-weight: 700; @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 0 0 rgba(0, 0, 0, 0)); @include transition(background-color .15s, box-shadow .15s); &.disabled { - border: 1px solid $lightGrey !important; + border: 1px solid $gray-l1 !important; border-radius: 3px !important; - background: $lightGrey !important; - color: $darkGrey !important; + background: $gray-l1 !important; + color: $gray-d1 !important; pointer-events: none; cursor: none; &:hover { @@ -33,34 +27,119 @@ } } -@mixin blue-button { +// button - green +@mixin green-button { @include button; - border: 1px solid #437fbf; + border: 1px solid $green-d1; border-radius: 3px; @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); - background-color: $blue; - color: #fff; + background-color: $green; + @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); + color: $white; - &:hover, &.active { - background-color: #62aaf5; - color: #fff; + &:hover { + background-color: $green-s1; + color: $white; + } + + &.disabled { + border: 1px solid $green-l3 !important; + background: $green-l3 !important; + color: $white !important; + @include box-shadow(none); } } -@mixin green-button { - @include button; - border: 1px solid #0d7011; - border-radius: 3px; - @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); - background-color: $green; - color: #fff; +// button - blue +@mixin blue-button { + @include button; + border: 1px solid $blue-d1; + border-radius: 3px; + @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); + background-color: $blue; + color: $white; - &:hover { - background-color: #129416; - color: #fff; - } + &:hover, &.active { + background-color: $blue-s2; + color: $white; + } + + &.disabled { + border: 1px solid $blue-l3 !important; + background: $blue-l3 !important; + color: $white !important; + @include box-shadow(none); + } } +// button - red +@mixin red-button { + @include button; + border: 1px solid $red-d1; + border-radius: 3px; + @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); + background-color: $red; + color: $white; + + &:hover, &.active { + background-color: $red-s1; + color: $white; + } + + &.disabled { + border: 1px solid $red-l3 !important; + background: $red-l3 !important; + color: $white !important; + @include box-shadow(none); + } +} + +// button - pink +@mixin pink-button { + @include button; + border: 1px solid $pink-d1; + border-radius: 3px; + @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); + background-color: $pink; + color: $white; + + &:hover, &.active { + background-color: $pink-s1; + color: $white; + } + + &.disabled { + border: 1px solid $pink-l3 !important; + background: $pink-l3 !important; + color: $white !important; + @include box-shadow(none); + } +} + +// button - orange +@mixin orange-button { + @include button; + border: 1px solid $orange-d1; + border-radius: 3px; + @include linear-gradient(top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0) 60%); + background-color: $orange; + @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); + color: $gray-d2; + + &:hover { + background-color: $orange-s2; + color: $gray-d2; + } + + &.disabled { + border: 1px solid $orange-l3 !important; + background: $orange-l2 !important; + color: $gray-l1 !important; + @include box-shadow(none); + } +} + +// button - white @mixin white-button { @include button; border: 1px solid $mediumGrey; @@ -77,24 +156,10 @@ } } -@mixin orange-button { - @include button; - border: 1px solid #bda046; - border-radius: 3px; - @include linear-gradient(top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0) 60%); - background-color: #edbd3c; - @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); - color: #3c3c3c; - - &:hover { - background-color: #ffcd46; - color: #3c3c3c; - } -} - +// button - grey @mixin grey-button { @include button; - border: 1px solid $darkGrey; + border: 1px solid $gray-d2; border-radius: 3px; @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); background-color: #d1dae3; @@ -107,42 +172,25 @@ } } -@mixin green-button { - @include button; - border: 1px solid $darkGreen; - border-radius: 3px; - @include linear-gradient(top, rgba(255, 255, 255, .3), rgba(255, 255, 255, 0)); - background-color: $green; - @include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset); - color: #fff; - - &:hover { - background-color: $brightGreen; - color: #fff; - } - - &.disabled { - border: 1px solid $disabledGreen !important; - background: $disabledGreen !important; - color: #fff !important; - @include box-shadow(none); - } -} - +// button - grey dark @mixin dark-grey-button { @include button; border: 1px solid #1c1e20; border-radius: 3px; background: -webkit-linear-gradient(top, rgba(255, 255, 255, .2), rgba(255, 255, 255, 0)) $extraDarkGrey; box-shadow: 0 1px 0 rgba(255, 255, 255, .2) inset; - color: #fff; + color: $white; &:hover { background-color: #595f64; - color: #fff; + color: $white; } } + +// ==================== + +// UI @mixin edit-box { padding: 15px 20px; border-radius: 3px; @@ -276,17 +324,9 @@ } } -@mixin sr-text { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} +// ==================== +// sunsetted mixins @mixin active { @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0)); background-color: rgba(255, 255, 255, .3); diff --git a/cms/templates/overview.html b/cms/templates/overview.html index 292246676b..c3a5a1d743 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -120,10 +120,34 @@

Alert Actions

+ +
+
+ + +
+
+ + +
+

A Newer Version of This Exists

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+ + @@ -160,7 +184,7 @@
-

We're Sorry, there was a error with Studio

+

We're sorry, there was a error with Studio

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

@@ -180,10 +204,7 @@

Alert Actions

@@ -204,7 +225,10 @@

Alert Actions

@@ -225,13 +249,25 @@
-
+

Your Studio account has been created, but needs to be activated

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+

Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.

+ +
diff --git a/common/static/sass/_mixins.scss b/common/static/sass/_mixins.scss index 76d52ed930..0c3eee602e 100644 --- a/common/static/sass/_mixins.scss +++ b/common/static/sass/_mixins.scss @@ -8,11 +8,16 @@ font-size: ($sizeValue/10) + rem; } + +// ==================== + // line-height @function lh($amount: 1) { @return $body-line-height * $amount; } +// ==================== + // image-replacement hidden text @mixin text-hide() { text-indent: 100%; @@ -32,6 +37,8 @@ width: 1px; } +// ==================== + // vertical and horizontal centering @mixin vertically-and-horizontally-centered ($height, $width) { left: 50%; @@ -43,6 +50,8 @@ top: 150px; } +// ==================== + // sizing @mixin size($width: $baseline, $height: $baseline) { height: $height; @@ -53,6 +62,8 @@ @include size($size); } +// ==================== + // placeholder styling @mixin placeholder($color) { :-moz-placeholder { @@ -64,4 +75,27 @@ :-ms-input-placeholder { color: $color; } +} + +// utility +@mixin clearfix { + &:after { + content: ''; + display: block; + height: 0; + visibility: hidden; + clear: both; + } +} + +// sunsetted mixins +@mixin sr-text { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } \ No newline at end of file From 60f803f3694e6effdc45c7eb128c8a176f3015e8 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 22 Feb 2013 14:29:19 -0500 Subject: [PATCH 006/483] studio - alerts: initial pass at alert styling and markup complete --- cms/templates/overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/templates/overview.html b/cms/templates/overview.html index c3a5a1d743..bfab97d3a0 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -384,4 +384,4 @@
- + \ No newline at end of file From b1f5d67a9619b8c3af5db3cd16b867ff1f81585e Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 22 Feb 2013 14:39:49 -0500 Subject: [PATCH 007/483] studio - alerts: initial pass at alert styling and markup complete --- cms/static/sass/_alerts.scss | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 393539e90d..9774a5e4ba 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -266,14 +266,8 @@ } .action-primary { - @include transition (opacity 0.25s ease-in-out); @include font-size(13); font-weight: 600; - opacity: 0.90; - - &:hover { - opacity: 1.0; - } } .action-secondary { From 8179b9fadc12e75610b64d9ce40d953755e9f5e9 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 25 Feb 2013 14:19:22 -0500 Subject: [PATCH 008/483] studio - alerts: initial pass at alert styling and markup complete --- cms/static/js/base.js | 19 + cms/static/sass/_alerts.scss | 297 ++++++++++--- cms/static/sass/_base.scss | 21 +- cms/static/sass/_keyframes.scss | 32 +- cms/static/sass/base-style.scss | 2 +- cms/templates/overview.html | 753 ++++++++++++++++++-------------- 6 files changed, 738 insertions(+), 386 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index d8b32cb0e8..c2080c87f9 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -43,6 +43,25 @@ $(document).ready(function () { $('body').addClass('js'); + // notifications + $('.testing .test-notification').click(function(e) { + (e).preventDefault(); + manageNotification(e); + }); + + function manageNotification(e) { + var $notificationRibbon = $('.wrapper-notification'); + + // showing + $notificationRibbon.toggleClass('is-shown'); + + // controls for closing notification + $notificationRibbon.find('.action-notification-close').click(function(e) { + (e).preventDefault(); + $notificationRibbon.toggleClass('is-shown'); + }); + } + // lean/simple modal $('a[rel*=modal]').leanModal({overlay : 0.80, closeButton: '.action-modal-close' }); $('a.action-modal-close').click(function(e){ diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 9774a5e4ba..de025e1be2 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -5,17 +5,19 @@ .wrapper-notification { @include clearfix(); @include box-sizing(border-box); - @include transition (bottom 2.0s ease-in-out 5s, opacity 2.0s ease-in-out 5s); - @include box-shadow(0 -1px 2px rgba(0,0,0,0.1)); + // @include transition (bottom 1.5s ease-in-out 0.25s); + transition: bottom 1.5s ease-in-out 0.25s; + -webkit-transition: bottom 1.5s ease-in-out 0.25s; + @include box-shadow(0 -1px 3px $shadow); position: fixed; - bottom: -100px; + bottom: -1000px; z-index: 1000; width: 100%; overflow: hidden; opacity: 0; - border-top: 1px solid $gray-d1; - padding: $baseline ($baseline*2); - background: $white; + border-top: 4px solid $gray-l1; + padding: ($baseline*0.75) ($baseline*2); + background: $gray-d3; &.is-shown { bottom: 0; @@ -23,78 +25,263 @@ } &.wrapper-notification-warning { - border-color: shade($yellow, 25%); - background: tint($yellow, 25%); + border-top-color: $orange; + + .icon-warning { + color: $orange; + } + + &:hover { + border-top-color: $orange-s2; + + .icon-warning { + color: $orange-s2; + } + } } &.wrapper-notification-error { - border-color: shade($red, 50%); - background: tint($red, 20%); - color: $white; + border-top-color: $red-l1; + + .icon-error { + color: $red-l1; + } + + &:hover { + border-top-color: $red; + + .icon-error { + color: $red; + } + } } - &.wrapper-notification-confirm { - border-color: shade($green, 30%); - background: tint($green, 40%); - color: shade($green, 30%); + &.wrapper-notification-confirmation { + border-top-color: $green; + + .icon-error { + color: $green; + } + + &:hover { + border-top-color: $green-s1; + + .icon-error { + color: $green-s1; + } + } + } + + // shorter/status notifications + &.wrapper-notification-status { + width: ($baseline*10); + right: ($baseline); + padding: ($baseline/2) $baseline; + + .notification { + background: red; + @include box-sizing(border-box); + @include clearfix(); + width: 100%; + max-width: none; + min-width: none; + + .icon { + width: auto; + } + + .copy { + width: auto; + } + } } } .notification { @include box-sizing(border-box); + @include clearfix(); margin: 0 auto; width: flex-grid(12); max-width: $fg-max-width; min-width: $fg-min-width; - .copy { + strong { + font-weight: 700; + } + + .icon, .copy { float: left; - width: flex-grid(9, 12); + } + + .icon { + @include transition (color 0.5s ease-in-out); + @include font-size(28); + width: flex-grid(1, 12); margin-right: flex-gutter(); - margin-top: 5px; - font-size: 14px; + text-align: right; + color: $white; + } - .icon { - display: inline-block; - vertical-align: top; - margin-right: 5px; - font-size: 20px; - } + .copy { + @include font-size(13); + width: flex-grid(10, 12); + color: $gray-l2; - p { - width: flex-grid(8, 9); - display: inline-block; - vertical-align: top; + .title { + @include font-size(14); + margin-bottom: 0; + color: $white; } } - .actions { - float: right; - width: flex-grid(3, 12); - margin-top: ($baseline/2); - text-align: right; + // with cancel + .action-notification-close { + @include transition(top .25s ease-in-out); + @include border-bottom-radius(3px); + position: absolute; + top: -($baseline/4); + left: ($baseline/2); + padding: ($baseline/4) ($baseline/2) 0 ($baseline/2); + background: $gray-d2; + text-align: center; - li { - display: inline-block; - vertical-align: middle; - margin-right: 10px; + .label { + @include text-sr(); + } - &:last-child { - margin-right: 0; + .ss-icon { + @include font-size(14); + color: $white; + } + + &:hover { + background: $blue; + top: 0; + } + } + + // with actions + &.has-actions { + + .icon { + width: flex-grid(1, 12); + } + + .copy { + width: flex-grid(7, 12); + margin-right: flex-gutter(); + } + + .nav-actions { + width: flex-grid(4, 12); + float: right; + margin-top: ($baseline/2); + text-align: right; + + .nav-item { + display: inline-block; + vertical-align: middle; + margin-right: ($baseline/2); + + &:last-child { + margin-right: 0; + } + + .action-primary { + @include font-size(13); + font-weight: 600; + } + + .action-secondary { + @include font-size(13); + } + } + } + } + + // notification types + &.warning { + + .action-notification-close { + + &:hover { + background: $orange; } } - .save-button { - @include blue-button; + .action-primary { + @include orange-button; + border-color: $orange-d2; } - .cancel-button { - @include white-button; + a { + color: $orange; + + &:hover { + color: $orange-s2; + } } } - strong { - font-weight: 700; + &.error { + + .action-notification-close { + + &:hover { + background: $red-l1; + } + } + + .action-primary { + @include red-button; + border-color: $red-d2; + } + + a { + color: $red-l1; + + &:hover { + color: $red; + } + } + } + + &.confirmation { + + .action-notification-close { + + &:hover { + background: $green; + } + } + + .action-primary { + @include green-button; + border-color: $green-d2; + } + + a { + color: $green; + + &:hover { + color: $green-s2; + } + } + } + + &.announcement { + + .action-notification-close { + + &:hover { + background: $blue; + } + } + + .action-primary { + @include blue-button; + border-color: $blue-d2; + } } } @@ -103,10 +290,9 @@ // alerts .wrapper-alert { @include box-sizing(border-box); - @include transition (opacity 2.0s ease-in-out 5s, border 0.5s ease-in-out); - @include box-shadow(0 1px 1px $white, inset 0 2px 5px $shadow-d1); + @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1); position: relative; - top: -($baseline*1.5); + top: -($baseline*20); z-index: 100; overflow: hidden; width: 100%; @@ -117,7 +303,6 @@ background: $gray-d3; &.is-shown { - bottom: 0; opacity: 1.0; } @@ -222,14 +407,14 @@ .icon { @include transition (color 0.5s ease-in-out); - @include font-size(28); + @include font-size(22); width: flex-grid(1, 12); margin-right: flex-gutter(); text-align: right; } .copy { - @include font-size(14); + @include font-size(13); width: flex-grid(10, 12); color: $gray-l2; @@ -332,14 +517,6 @@ @include blue-button; border-color: $blue-d2; } - - a { - color: $blue; - - &:hover { - color: $blue-s2; - } - } } &.step-required { diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 62f72e8d63..d2cdda443f 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -25,7 +25,7 @@ a { @include transition(color .15s); &:hover { - color: #cb9c40; + color: $orange-d1; } } @@ -885,7 +885,7 @@ body.js { // ==================== -// works in progress +// works in progress & testing body.hide-wip { .wip-box { @@ -893,6 +893,23 @@ body.hide-wip { } } +.wrapper-testing { + background: $black; + width: 100%; + height: auto; + + .testing { + @include font-size(14); + position: relative; + max-width: $fg-max-width; + min-width: $fg-min-width; + width: flex-grid(12); + margin: 0 auto $baseline auto; + padding: $baseline; + color: $white; + } +} + // ==================== // needed fudges for now diff --git a/cms/static/sass/_keyframes.scss b/cms/static/sass/_keyframes.scss index 7661f18980..394548a2e7 100644 --- a/cms/static/sass/_keyframes.scss +++ b/cms/static/sass/_keyframes.scss @@ -1,3 +1,33 @@ +// studio animations & keyframes +// ==================== + +// rotate clockwise +@mixin rotateClockwise { + 0% { + @include transform(rotate(0deg)); + } + + 25% { + @include transform(rotate(90deg)); + } + + 50% { + @include transform(rotate(180deg)); + } + + 100% { + @include transform(rotate(360deg)); + } +} + +@-moz-keyframes rotateClockwise { @include rotateClockwise(); } +@-webkit-keyframes rotateClockwise { @include rotateClockwise(); } +@-o-keyframes rotateClockwise { @include rotateClockwise(); } +@keyframes rotateClockwise { @include rotateClockwise();} + +// ==================== + +// bounce in @mixin bounce-in { 0% { opacity: 0; @@ -24,4 +54,4 @@ @include animation-duration($duration); @include animation-timing-function($timing); @include animation-fill-mode(both); -} +} \ No newline at end of file diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index dceac4233d..075e0e87a5 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -1,5 +1,6 @@ @import 'bourbon/bourbon'; @import 'bourbon/addons/button'; +@import "variables"; @import 'vendor/normalize'; @import 'keyframes'; @@ -7,7 +8,6 @@ @import 'mixins'; @import "fonts"; -@import "variables"; @import "cms_mixins"; @import "extends"; @import "base"; diff --git a/cms/templates/overview.html b/cms/templates/overview.html index bfab97d3a0..2950280702 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -13,375 +13,484 @@ <%namespace name="units" file="widgets/units.html" /> <%block name="jsextra"> - - - - - - - - + + + + + + + + - + $(".gradable-status").each(function(index, ele) { + var gradeView = new CMS.Views.OverviewAssignmentGrader({ + el : ele, + graders : window.graderTypes + }); + }); +}); + + <%block name="header_extras"> - - - - - + + + + + + + <%block name="content"> - -
-
- + +
+
+ -
+

You are editing a draft

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
- - +

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

A Newer Version of This Exists

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

Your changes have been saved

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

+
- -
-
- + +
+
+ -
-

Your changes have been saved

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

-
+
+

X Has been removed

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
- -
-
- + +
+
+ -
-

X Has been removed

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
+
+

We're sorry, there was a error with Studio

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
- -
-
- + +
+
+ -
-

We're sorry, there was a error with Studio

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
+
+

There was an error in your submission

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

+
+ + +
+
+ + +
+
+ 📢 + +
+

Studio will be unavailable this weekend

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+ + +
+
+ + +
+
+ 📢 + +
+

Studio will be unavailable this weekend

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
- -
-
- + +
+
+ -
-

There was an error in your submission

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

-
- - +
+

Your Studio account has been created, but needs to be activated

+

Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.

+ +
+
- -
-
- 📢 - -
-

Studio will be unavailable this weekend

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
- - -
-
- - -
-
- 📢 - -
-

Studio will be unavailable this weekend

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+

Section Release Date

+
+ + +
+

On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

+ SaveCancel
+
- -
-
- +
+ +
-
-

Your Studio account has been created, but needs to be activated

-

Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.

-
- - +
+
+
+ Course Content +

Course Outline

-
-
-
-

Section Release Date

-
- - -
-

On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

-
-
- SaveCancel -
-
+ + +
-
-
-
- Course Content -

Course Outline

-
+
+
+
- -
-
-
- % for section in sections: -
-
- - -
-

- ${section.display_name} - -

- -
- -
- - -
-
-
- -
    - % for subsection in section.get_children(): - - % endfor -
+
+

+ ${section.display_name} + +

+
-
- % endfor -
+ +
+ + +
+
+
+ +
    + % for subsection in section.get_children(): + + % endfor +
+
+ + % endfor + +
+
+
+ + +
+
+ 📝 + +
+

You've Made Some Changes

+

Note: Your changes will not take effect until you save your progress.

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

A Newer Version of This Exists

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+ + + + + + close notification + +
+
+ + +
+
+ + +
+

Are You Sure You Want to Edit That?

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

Saving

+

Hamster wheels are turning pretty fast right now. Hang on! Saving will be done soon.

-
+
+ + +
+
+ + +
+

Fun Fact:

+

Using the checkmark will allow you make a subsection gradable as an assignment, which counts towards a student's total grade

+
+
+
\ No newline at end of file From d809df913daf3f5d83753922d8f157c94ff217d8 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 25 Feb 2013 14:45:27 -0500 Subject: [PATCH 009/483] studio - alerts: moved all states and documentation into new /alerts view --- cms/djangoapps/contentstore/views.py | 3 + cms/static/sass/_alerts.scss | 18 +- cms/static/sass/_base.scss | 27 +++ cms/templates/alerts.html | 326 +++++++++++++++++++++++++++ cms/templates/overview.html | 273 ---------------------- cms/urls.py | 1 + 6 files changed, 374 insertions(+), 274 deletions(-) create mode 100644 cms/templates/alerts.html diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 6d5905afe7..2e30751d30 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -106,6 +106,9 @@ def howitworks(request): else: return render_to_response('howitworks.html', {}) +def alerts(request): + return render_to_response('alerts.html', {}) + # ==== Views for any logged-in user ================================== @login_required diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index de025e1be2..d211d0d2ed 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -292,7 +292,7 @@ @include box-sizing(border-box); @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1); position: relative; - top: -($baseline*20); + top: -($baseline*1.5); z-index: 100; overflow: hidden; width: 100%; @@ -536,6 +536,22 @@ } } +// temporary +body.uxdesign.alerts { + + .content-primary, .content-supplementary { + @include box-sizing(border-box); + float: left; + } + + .content-primary { + @extend .window; + width: flex-grid(12, 12); + margin-right: flex-gutter(); + padding: $baseline ($baseline*1.5); + } +} + // artifact styles // .alert { // padding: 15px 20px; diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index d2cdda443f..0d01a0b24b 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -339,11 +339,38 @@ h1 { .title-5 { } + + > section { + margin: 0 0 $baseline 0; + + header { + @include clearfix(); + + .title-2 { + width: flex-grid(5, 12); + margin: 0 flex-gutter() 0 0; + float: left; + } + + .tip { + @include font-size(13); + width: flex-grid(7, 12); + float: right; + margin-top: ($baseline/2); + text-align: right; + color: $gray-l2; + } + } + } } // layout - supplemental content .content-supplementary { + > section { + margin: 0 0 $baseline 0; + } + .bit { @include font-size(13); margin: 0 0 $baseline 0; diff --git a/cms/templates/alerts.html b/cms/templates/alerts.html new file mode 100644 index 0000000000..efdac8e761 --- /dev/null +++ b/cms/templates/alerts.html @@ -0,0 +1,326 @@ +<%inherit file="base.html" /> +<%block name="title">Studio Alerts +<%block name="bodyclass">is-signedin course uxdesign alerts + +<%block name="content"> + +
+
+ + +
+

You are editing a draft

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

A Newer Version of This Exists

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

Your changes have been saved

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

+
+
+
+ + +
+
+ + +
+

X Has been removed

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+
+ + +
+
+ + +
+

We're sorry, there was a error with Studio

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+
+ + +
+
+ + +
+

There was an error in your submission

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

+
+ + +
+
+ + +
+
+ 📢 + +
+

Studio will be unavailable this weekend

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+ + +
+
+ + +
+
+ 📢 + +
+

Studio will be unavailable this weekend

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+
+
+ + +
+
+ + +
+

Your Studio account has been created, but needs to be activated

+

Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.

+
+ + +
+
+ +
+
+

Section Release Date

+
+ + +
+

On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

+
+
+ SaveCancel +
+
+ +
+ +
+ +
+
+
+ UX Design +

Alerts & Notifications

+
+
+
+ +
+
+
+
+
+

Alerts

+ persistant, static messages to the user +
+ +

In Studio, alerts are 1) general warnings/notes (e.g. drafts, published content, next steps) about the current view a user is interacting with or 2) notes about the status (e.g. saved confirmations, errors, next system steps) of any previous state that need to communicated to the user when arriving at the current view.

+
+ +
+
+

Notifications

+ contextual, feedback-based, and temporal messages to the user +
+ +

In Studio, notifications are meant to inform the user of 1) any system status (e.g. saving, processing/validating) occurring based on any action they have taken or 2) any decisions (e.g. saving/discarding) a user must make to confirm.

+
+
+
+
+ + +
+
+ 📝 + +
+

You've Made Some Changes

+

Note: Your changes will not take effect until you save your progress.

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

A Newer Version of This Exists

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

+
+ + + + + + close notification + +
+
+ + +
+
+ + +
+

Are You Sure You Want to Edit That?

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

Saving

+

Hamster wheels are turning pretty fast right now. Hang on! Saving will be done soon.

+
+
+
+ + +
+
+ + +
+

Fun Fact:

+

Using the checkmark will allow you make a subsection gradable as an assignment, which counts towards a student's total grade

+
+
+
+ \ No newline at end of file diff --git a/cms/templates/overview.html b/cms/templates/overview.html index 2950280702..1792cbc844 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -106,171 +106,6 @@ $(document).ready(function(){ <%block name="content"> - -
-
- - -
-

You are editing a draft

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

A Newer Version of This Exists

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

Your changes have been saved

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

-
-
-
- - -
-
- - -
-

X Has been removed

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
-
-
- - -
-
- - -
-

We're sorry, there was a error with Studio

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
-
-
- - -
-
- - -
-

There was an error in your submission

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

-
- - -
-
- - -
-
- 📢 - -
-

Studio will be unavailable this weekend

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
- - -
-
- - -
-
- 📢 - -
-

Studio will be unavailable this weekend

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
-
-
- - -
-
- - -
-

Your Studio account has been created, but needs to be activated

-

Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.

-
- - -
-
-

Section Release Date

@@ -285,14 +120,6 @@ $(document).ready(function(){
-
- -
-
@@ -393,104 +220,4 @@ $(document).ready(function(){
- -
-
- 📝 - -
-

You've Made Some Changes

-

Note: Your changes will not take effect until you save your progress.

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

A Newer Version of This Exists

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

-
- - - - - - close notification - -
-
- - -
-
- - -
-

Are You Sure You Want to Edit That?

-

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

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

Saving

-

Hamster wheels are turning pretty fast right now. Hang on! Saving will be done soon.

-
-
-
- - -
-
- - -
-

Fun Fact:

-

Using the checkmark will allow you make a subsection gradable as an assignment, which counts towards a student's total grade

-
-
-
\ No newline at end of file diff --git a/cms/urls.py b/cms/urls.py index 35b2707241..5c8bd42b10 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -78,6 +78,7 @@ urlpatterns = ('', # User creation and updating views urlpatterns += ( + url(r'^alerts$', 'contentstore.views.alerts', name='alerts'), url(r'^howitworks$', 'contentstore.views.howitworks', name='howitworks'), url(r'^signup$', 'contentstore.views.signup', name='signup'), From 95ea01689f2a0ed955a173192191da8f8040f973 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 25 Feb 2013 16:30:36 -0500 Subject: [PATCH 010/483] studio - alerts: firmed up status-based notifications --- cms/static/sass/_alerts.scss | 62 +++++++++++++++++++++++---------- cms/static/sass/_base.scss | 40 ++++++++------------- cms/static/sass/_keyframes.scss | 8 ----- cms/templates/alerts.html | 57 ++++++++++++++++++------------ 4 files changed, 93 insertions(+), 74 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index d211d0d2ed..8e5401112b 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -10,20 +10,12 @@ -webkit-transition: bottom 1.5s ease-in-out 0.25s; @include box-shadow(0 -1px 3px $shadow); position: fixed; - bottom: -1000px; z-index: 1000; width: 100%; - overflow: hidden; - opacity: 0; border-top: 4px solid $gray-l1; padding: ($baseline*0.75) ($baseline*2); background: $gray-d3; - &.is-shown { - bottom: 0; - opacity: 1.0; - } - &.wrapper-notification-warning { border-top-color: $orange; @@ -74,12 +66,11 @@ // shorter/status notifications &.wrapper-notification-status { - width: ($baseline*10); + width: ($baseline*12.5); right: ($baseline); padding: ($baseline/2) $baseline; .notification { - background: red; @include box-sizing(border-box); @include clearfix(); width: 100%; @@ -87,11 +78,19 @@ min-width: none; .icon { - width: auto; + width: $baseline; + margin-right: ($baseline*0.75); } .copy { - width: auto; + width: ($baseline*9); + + p { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 100%; + } } } } @@ -115,7 +114,7 @@ .icon { @include transition (color 0.5s ease-in-out); - @include font-size(28); + @include font-size(22); width: flex-grid(1, 12); margin-right: flex-gutter(); text-align: right; @@ -283,6 +282,16 @@ border-color: $blue-d2; } } + + &.saving { + + .icon-saving { + @include animation(rotateClockwise 3.0s forwards linear infinite); + width: 22px; + height: 25px; + line-height: 3rem !important; + } + } } // ==================== @@ -296,16 +305,11 @@ z-index: 100; overflow: hidden; width: 100%; - opacity: 0; border-bottom: 4px solid $gray-l1; border-top: 1px solid $black; padding: $baseline ($baseline*2); background: $gray-d3; - &.is-shown { - opacity: 1.0; - } - &.wrapper-alert-warning { border-bottom-color: $orange; @@ -536,6 +540,28 @@ } } +// js enabled +.js { + + .wrapper-alert { + display: none; + + &.is-shown { + display: block; + } + } + + .wrapper-notification { + bottom: -1000px; + opacity: 0; + + &.is-shown { + bottom: 0; + opacity: 1.0; + } + } +} + // temporary body.uxdesign.alerts { diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 0d01a0b24b..d5a8adc6cb 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -69,7 +69,7 @@ h1 { } .title-3 { - @include font-size(16); + @include font-size(18); margin-bottom: ($baseline/2); } @@ -93,6 +93,14 @@ h1 { font-weight: 500 } +p, ul, ol, dl { + margin-bottom: ($baseline/2); + + &:last-child { + margin-bottom: 0; + } +} + // ==================== // layout - basic page header @@ -316,32 +324,12 @@ h1 { color: $gray-d3; } - .title-1 { - - } - - .title-2 { - @include font-size(24); - margin: 0 0 ($baseline/2) 0; - font-weight: 600; - } - - .title-3 { - @include font-size(16); - margin: 0 0 ($baseline/4) 0; - font-weight: 500; - } - - .title-4 { - - } - - .title-5 { - - } - > section { - margin: 0 0 $baseline 0; + margin: 0 0 ($baseline*2) 0; + + &:last-child { + margin-bottom: 0; + } header { @include clearfix(); diff --git a/cms/static/sass/_keyframes.scss b/cms/static/sass/_keyframes.scss index 394548a2e7..0ae1d78ffe 100644 --- a/cms/static/sass/_keyframes.scss +++ b/cms/static/sass/_keyframes.scss @@ -7,14 +7,6 @@ @include transform(rotate(0deg)); } - 25% { - @include transform(rotate(90deg)); - } - - 50% { - @include transform(rotate(180deg)); - } - 100% { @include transform(rotate(360deg)); } diff --git a/cms/templates/alerts.html b/cms/templates/alerts.html index efdac8e761..869bad7260 100644 --- a/cms/templates/alerts.html +++ b/cms/templates/alerts.html @@ -4,7 +4,7 @@ <%block name="content"> -
+
@@ -28,7 +28,7 @@
-
+
@@ -52,7 +52,7 @@
-
+
@@ -64,7 +64,7 @@
-
+
@@ -76,7 +76,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
@@ -109,7 +109,7 @@
-
+
📢 @@ -133,7 +133,7 @@
-
+
📢 @@ -145,7 +145,7 @@
-
+
@@ -182,14 +182,6 @@
-
- -
-
@@ -209,6 +201,17 @@

In Studio, alerts are 1) general warnings/notes (e.g. drafts, published content, next steps) about the current view a user is interacting with or 2) notes about the status (e.g. saved confirmations, errors, next system steps) of any previous state that need to communicated to the user when arriving at the current view.

+ +

Different Static Examples of Alerts

+

Note: alerts will probably never been shown based on click or page action and will primarily be loaded along with a pageload and initial display

+ +
@@ -218,13 +221,23 @@

In Studio, notifications are meant to inform the user of 1) any system status (e.g. saving, processing/validating) occurring based on any action they have taken or 2) any decisions (e.g. saving/discarding) a user must make to confirm.

+ +

Different Static Examples of Notifications

+ +
-
+
📝 @@ -248,7 +261,7 @@
-
+
@@ -277,7 +290,7 @@
-
+
@@ -301,7 +314,7 @@
-
+
@@ -313,7 +326,7 @@
-
+
From 0926395445cf20e15edfd4b0f80e1359994e1ac8 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 26 Feb 2013 12:32:20 -0500 Subject: [PATCH 011/483] studio - alerts: added in close buttons and basic JS to control them/moved demo JS to alerts.html page --- cms/static/js/base.js | 35 ++++----- cms/static/sass/_alerts.scss | 137 +++++++++++++++++++++++++---------- cms/templates/alerts.html | 79 ++++++++++++++++---- 3 files changed, 179 insertions(+), 72 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index c2080c87f9..21571a8f4a 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -43,31 +43,28 @@ $(document).ready(function () { $('body').addClass('js'); - // notifications - $('.testing .test-notification').click(function(e) { - (e).preventDefault(); - manageNotification(e); - }); - - function manageNotification(e) { - var $notificationRibbon = $('.wrapper-notification'); - - // showing - $notificationRibbon.toggleClass('is-shown'); - - // controls for closing notification - $notificationRibbon.find('.action-notification-close').click(function(e) { - (e).preventDefault(); - $notificationRibbon.toggleClass('is-shown'); - }); - } - // lean/simple modal $('a[rel*=modal]').leanModal({overlay : 0.80, closeButton: '.action-modal-close' }); $('a.action-modal-close').click(function(e){ (e).preventDefault(); }); + // alert and notifications - manual close + $('.action-alert-close').click(function(e) { + (e).preventDefault(); + console.log('closing alert'); + $(this).closest('.wrapper-alert').removeClass('is-shown'); + }); + + // alert and notifications - manual close + $('.action-notification-close').click(function(e) { + (e).preventDefault(); + $(this).closest('.wrapper-notification').removeClass('is-shown'); + }); + + + + // nav - dropdown related $body.click(function (e) { $('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown'); diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 8e5401112b..5b9a08b0ea 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -5,9 +5,8 @@ .wrapper-notification { @include clearfix(); @include box-sizing(border-box); - // @include transition (bottom 1.5s ease-in-out 0.25s); - transition: bottom 1.5s ease-in-out 0.25s; - -webkit-transition: bottom 1.5s ease-in-out 0.25s; + transition: bottom 1.0s ease-in-out 0.125s; + -webkit-transition: bottom 1.0s ease-in-out 0.125s; @include box-shadow(0 -1px 3px $shadow); position: fixed; z-index: 1000; @@ -66,8 +65,9 @@ // shorter/status notifications &.wrapper-notification-status { - width: ($baseline*12.5); + width: ($baseline*6); right: ($baseline); + border-top-color: $pink; padding: ($baseline/2) $baseline; .notification { @@ -94,6 +94,30 @@ } } } + + // shorter/status notifications + &.wrapper-notification-help { + width: ($baseline*14); + right: ($baseline); + padding: ($baseline/2) $baseline; + + .notification { + @include box-sizing(border-box); + @include clearfix(); + width: 100%; + max-width: none; + min-width: none; + + .icon-help { + width: $baseline; + margin-right: ($baseline*0.75); + } + + .copy { + width: ($baseline*11); + } + } + } } .notification { @@ -135,27 +159,24 @@ // with cancel .action-notification-close { - @include transition(top .25s ease-in-out); - @include border-bottom-radius(3px); + @include border-top-radius(3px); position: absolute; - top: -($baseline/4); - left: ($baseline/2); + top: -31px; + right: $baseline; padding: ($baseline/4) ($baseline/2) 0 ($baseline/2); - background: $gray-d2; + background: $gray-l1; text-align: center; .label { @include text-sr(); } - .ss-icon { + .icon { @include font-size(14); color: $white; - } - - &:hover { - background: $blue; - top: 0; + width: auto; + margin: 0; + padding: 2px; } } @@ -185,31 +206,35 @@ &:last-child { margin-right: 0; } - - .action-primary { - @include font-size(13); - font-weight: 600; - } - - .action-secondary { - @include font-size(13); - } } } + + .action-primary { + @include blue-button(); + @include font-size(13); + border-color: $blue-d2; + font-weight: 600; + } + + .action-secondary { + @include font-size(13); + } } // notification types &.warning { .action-notification-close { + background: $orange; &:hover { - background: $orange; + background: $orange-s2; } } .action-primary { - @include orange-button; + @include orange-button(); + @include font-size(13); border-color: $orange-d2; } @@ -225,14 +250,16 @@ &.error { .action-notification-close { + background: $red-l1; &:hover { - background: $red-l1; + background: $red; } } .action-primary { - @include red-button; + @include red-button(); + @include font-size(13); border-color: $red-d2; } @@ -248,14 +275,16 @@ &.confirmation { .action-notification-close { + background: $green; &:hover { - background: $green; + background: $green-s2; } } .action-primary { - @include green-button; + @include green-button(); + @include font-size(13); border-color: $green-d2; } @@ -271,14 +300,16 @@ &.announcement { .action-notification-close { + background: $blue; &:hover { - background: $blue; + background: $blue-s1; } } .action-primary { - @include blue-button; + @include blue-button(); + @include font-size(13); border-color: $blue-d2; } } @@ -291,6 +322,10 @@ height: 25px; line-height: 3rem !important; } + + .copy p { + @include text-sr(); + } } } @@ -428,6 +463,7 @@ } } + // with actions &.has-actions { .icon { @@ -466,11 +502,38 @@ } } + // with cancel + .action-alert-close { + @include border-bottom-radius(3px); + position: absolute; + top: -($baseline/10); + right: $baseline; + padding: ($baseline/4) ($baseline/2) 0 ($baseline/2); + background: $gray-d1; + text-align: center; + + .label { + @include text-sr(); + } + + .icon { + @include font-size(14); + color: $white; + width: auto; + margin: 0; + padding: 2px; + } + + &:hover { + background: $gray-l1; + } + } + // alert types &.warning { .action-primary { - @include orange-button; + @include orange-button(); border-color: $orange-d2; } @@ -486,7 +549,7 @@ &.error { .action-primary { - @include red-button; + @include red-button(); border-color: $red-d2; } @@ -502,7 +565,7 @@ &.confirmation { .action-primary { - @include green-button; + @include green-button(); border-color: $green-d2; } @@ -518,7 +581,7 @@ &.announcement { .action-primary { - @include blue-button; + @include blue-button(); border-color: $blue-d2; } } @@ -527,7 +590,7 @@ .action-primary { border-color: $pink-d2; - @include pink-button; + @include pink-button(); } a { diff --git a/cms/templates/alerts.html b/cms/templates/alerts.html index 869bad7260..a783a6f2fe 100644 --- a/cms/templates/alerts.html +++ b/cms/templates/alerts.html @@ -2,6 +2,26 @@ <%block name="title">Studio Alerts <%block name="bodyclass">is-signedin course uxdesign alerts +<%block name="jsextra"> + + + <%block name="content">
@@ -63,6 +83,23 @@
+ +
+
+ + +
+

Your changes have been saved

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Please see below

+
+ + + + close alert + +
+
+
@@ -206,11 +243,16 @@

Note: alerts will probably never been shown based on click or page action and will primarily be loaded along with a pageload and initial display

@@ -225,11 +267,11 @@

Different Static Examples of Notifications

@@ -243,7 +285,7 @@

You've Made Some Changes

-

Note: Your changes will not take effect until you save your progress.

+

Your changes will not take effect until you save your progress.

-
+
@@ -325,15 +367,20 @@
- -
-
- + +
+
+

Fun Fact:

Using the checkmark will allow you make a subsection gradable as an assignment, which counts towards a student's total grade

+ + + + close notification +
\ No newline at end of file From d09927e8ddf9be8640fcae31541f0e761e737db7 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 26 Feb 2013 12:38:46 -0500 Subject: [PATCH 012/483] studio - alerts: small styling tweaks for the close button on help notifications --- cms/static/sass/_alerts.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 5b9a08b0ea..5364e637d7 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -113,8 +113,12 @@ margin-right: ($baseline*0.75); } + .action-notification-close { + right: 0; + } + .copy { - width: ($baseline*11); + width: ($baseline*10); } } } From cb456ad22875b8eac9e9b7e552158e40e702ad97 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Feb 2013 12:45:44 -0500 Subject: [PATCH 013/483] studio - alerts: converted transition-based animation to keyframe/css animation and added in a demo for a fleeting notification --- cms/static/js/base.js | 9 ++--- cms/static/sass/_alerts.scss | 62 ++++++++++++++++++++------------- cms/static/sass/_keyframes.scss | 44 +++++++++++++++++++++++ cms/static/sass/_variables.scss | 4 +++ cms/templates/alerts.html | 21 ++++++++--- 5 files changed, 105 insertions(+), 35 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 21571a8f4a..0bb0ae10f1 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -50,21 +50,18 @@ $(document).ready(function () { }); // alert and notifications - manual close - $('.action-alert-close').click(function(e) { + $('.action-alert-close, .alert.has-actions .nav-actions a').click(function(e) { (e).preventDefault(); console.log('closing alert'); $(this).closest('.wrapper-alert').removeClass('is-shown'); }); - // alert and notifications - manual close - $('.action-notification-close').click(function(e) { + // alert and notifications - manual & action-based close + $('.action-notification-close, .notification.has-actions .nav-actions a').click(function(e) { (e).preventDefault(); $(this).closest('.wrapper-notification').removeClass('is-shown'); }); - - - // nav - dropdown related $body.click(function (e) { $('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown'); diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 5364e637d7..a582c5cfcf 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -5,10 +5,9 @@ .wrapper-notification { @include clearfix(); @include box-sizing(border-box); - transition: bottom 1.0s ease-in-out 0.125s; - -webkit-transition: bottom 1.0s ease-in-out 0.125s; @include box-shadow(0 -1px 3px $shadow); position: fixed; + bottom: 0; z-index: 1000; width: 100%; border-top: 4px solid $gray-l1; @@ -31,7 +30,7 @@ } } - &.wrapper-notification-error { + &.wrapper-cation-error { border-top-color: $red-l1; .icon-error { @@ -50,24 +49,31 @@ &.wrapper-notification-confirmation { border-top-color: $green; - .icon-error { + .icon-confirmation { color: $green; } &:hover { border-top-color: $green-s1; - .icon-error { + .icon-confirmation { color: $green-s1; } } } + &.wrapper-notification-saving { + border-top-color: $pink; + + &:hover { + border-top-color: $pink-s1; + } + } + // shorter/status notifications &.wrapper-notification-status { - width: ($baseline*6); + width: ($baseline*8); right: ($baseline); - border-top-color: $pink; padding: ($baseline/2) $baseline; .notification { @@ -77,25 +83,26 @@ max-width: none; min-width: none; + .icon, .copy { + float: none; + display: inline-block; + vertical-align: middle; + } + .icon { width: $baseline; + height: ($baseline*1.25); margin-right: ($baseline*0.75); + line-height: 3rem; } .copy { - width: ($baseline*9); - p { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - width: 100%; - } } - } + } } - // shorter/status notifications + // help notifications &.wrapper-notification-help { width: ($baseline*14); right: ($baseline); @@ -138,12 +145,15 @@ .icon, .copy { float: left; + display: inline-block; + vertical-align: middle; } .icon { @include transition (color 0.5s ease-in-out); @include font-size(22); width: flex-grid(1, 12); + height: ($baseline*1.25); margin-right: flex-gutter(); text-align: right; color: $white; @@ -165,9 +175,9 @@ .action-notification-close { @include border-top-radius(3px); position: absolute; - top: -31px; + top: -34px; right: $baseline; - padding: ($baseline/4) ($baseline/2) 0 ($baseline/2); + padding: ($baseline/5) ($baseline/2.5) 0 ($baseline/2.5); background: $gray-l1; text-align: center; @@ -278,6 +288,10 @@ &.confirmation { + .copy { + margin-top: ($baseline/5); + } + .action-notification-close { background: $green; @@ -323,8 +337,6 @@ .icon-saving { @include animation(rotateClockwise 3.0s forwards linear infinite); width: 22px; - height: 25px; - line-height: 3rem !important; } .copy p { @@ -619,12 +631,14 @@ } .wrapper-notification { - bottom: -1000px; - opacity: 0; + bottom: -($notification-height); &.is-shown { - bottom: 0; - opacity: 1.0; + @include animation(notificationsSlideUp 2s forwards ease-in-out 1); + } + + &.is-fleeting.is-shown { + @include animation(notificationsSlideUpDown 6s forwards ease-in-out 1); } } } diff --git a/cms/static/sass/_keyframes.scss b/cms/static/sass/_keyframes.scss index 0ae1d78ffe..a13891160a 100644 --- a/cms/static/sass/_keyframes.scss +++ b/cms/static/sass/_keyframes.scss @@ -19,6 +19,50 @@ // ==================== +// notifications slide up +@mixin notificationsSlideUp { + 0% { + @include transform(translateY(0)); + } + + 90% { + @include transform(translateY(-($notification-height))); + } + + 100% { + @include transform(translateY(-($notification-height*0.99))); + } +} + +@-moz-keyframes notificationsSlideUp { @include notificationsSlideUp(); } +@-webkit-keyframes notificationsSlideUp { @include notificationsSlideUp(); } +@-o-keyframes notificationsSlideUp { @include notificationsSlideUp(); } +@keyframes notificationsSlideUp { @include notificationsSlideUp();} + +// ==================== + +// notifications slide up +@mixin notificationsSlideUpDown { + 0%, 100% { + @include transform(translateY(0)); + } + + 15%, 85% { + @include transform(translateY(-($notification-height))); + } + + 20%, 80% { + @include transform(translateY(-($notification-height*0.99))); + } +} + +@-moz-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); } +@-webkit-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); } +@-o-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); } +@keyframes notificationsSlideUpDown { @include notificationsSlideUpDown();} + +// ==================== + // bounce in @mixin bounce-in { 0% { diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index 907d5cbdb2..b89fa7cc18 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -136,6 +136,10 @@ $shadow-l1: rgba(0,0,0,0.1); $shadow-l2: rgba(0,0,0,0.05); $shadow-d1: rgba(0,0,0,0.4); + +// specific UI +$notification-height: ($baseline*10); + // colors - inherited $baseFontColor: #3c3c3c; $offBlack: #3c3c3c; diff --git a/cms/templates/alerts.html b/cms/templates/alerts.html index a783a6f2fe..3682cb3200 100644 --- a/cms/templates/alerts.html +++ b/cms/templates/alerts.html @@ -250,8 +250,8 @@
  • Toggle Previous View/Action Removed Alert
  • Toggle System Error Alert
  • Toggle User Error Alert
  • -
  • Toggle Announcement Alert
  • -
  • Toggle Announcement with Actions Alert
  • +
  • Toggle Announcement Alert
  • +
  • Toggle Announcement with Actions Alert
  • Toggle Activiation Alert
  • @@ -270,6 +270,7 @@
  • Toggle Change Warning Notification
  • Toggle New Version Warning Notification
  • Toggle Editing Warning Notification
  • +
  • Toggle Confirmation Notification
  • Toggle Saving Notification
  • Toggle Help Notification
  • @@ -356,13 +357,23 @@
    -
    +
    -

    Saving

    -

    Hamster wheels are turning pretty fast right now. Hang on! Saving will be done soon.

    +

    Saving …

    +
    +
    +
    + + +
    +
    + + +
    +

    Your Section Has Been Created

    From ad70581b80635999ebb610c26abbb2371c738888 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 1 Mar 2013 15:43:07 -0500 Subject: [PATCH 014/483] studio - alerts: more progress including prompts --- cms/static/js/base.js | 8 +- cms/static/sass/_alerts.scss | 82 +++++++++++++++++++- cms/static/sass/_keyframes.scss | 24 +++++- cms/static/sass/_variables.scss | 6 ++ cms/templates/alerts.html | 129 ++++++++++++++++++++++++++------ 5 files changed, 222 insertions(+), 27 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 0bb0ae10f1..9af785045f 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -59,7 +59,13 @@ $(document).ready(function () { // alert and notifications - manual & action-based close $('.action-notification-close, .notification.has-actions .nav-actions a').click(function(e) { (e).preventDefault(); - $(this).closest('.wrapper-notification').removeClass('is-shown'); + $(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding'); + }); + + // prompt close + $('.prompt .action-cancel').click(function(e) { + (e).preventDefault(); + $(this).closest('.wrapper-prompt').removeClass('is-shown').addClass('is-hiding'); }); // nav - dropdown related diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index a582c5cfcf..fc8b25ea2c 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -1,6 +1,43 @@ // studio alerts and notifications // ==================== +// prompts +.wrapper-prompt { + position: fixed; + top: 0; + background: $black-t1; + width: 100%; + height: 100%; + text-align: center; + z-index: 100; + + &:before { + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; + margin-right: -0.25em; /* Adjusts for spacing */ + } + + .prompt { + @include border-radius(3px); + @include box-sizing(border-box); + @include box-shadow(0 0 4px $shadow-d1); + display: inline-block; + vertical-align: middle; + width: $baseline*15; + padding: $baseline; + background: $white; + + .nav-actions { + + .nav-item { + + } + } + } +} + // notifications .wrapper-notification { @include clearfix(); @@ -622,6 +659,17 @@ // js enabled .js { + .wrapper-prompt { + @include transition (opacity 0.25s ease-in-out); + display: none; + opacity: 0; + + &.is-shown { + display: block; + opacity: 1.0; + } + } + .wrapper-alert { display: none; @@ -633,12 +681,17 @@ .wrapper-notification { bottom: -($notification-height); + // varying animations &.is-shown { - @include animation(notificationsSlideUp 2s forwards ease-in-out 1); + @include animation(notificationsSlideUp 1s forwards ease-in-out 1); } - &.is-fleeting.is-shown { - @include animation(notificationsSlideUpDown 6s forwards ease-in-out 1); + &.is-hiding { + @include animation(notificationsSlideDown 1s forwards ease-in-out 1); + } + + &.is-fleeting { + @include animation(notificationsSlideUpDown 2s forwards ease-in-out 1); } } } @@ -656,6 +709,29 @@ body.uxdesign.alerts { width: flex-grid(12, 12); margin-right: flex-gutter(); padding: $baseline ($baseline*1.5); + + ul { + + li { + @include clearfix(); + width: flex-grid(12, 12); + margin-bottom: ($baseline/4); + border-bottom: 1px solid $gray-l4; + padding-bottom: ($baseline/4); + + &:last-child { + margin-bottom: 0; + border-bottom: none; + padding-bottom: 0; + } + + a { + float: left; + width: flex-grid(5, 12); + margin-right: flex-gutter(); + } + } + } } } diff --git a/cms/static/sass/_keyframes.scss b/cms/static/sass/_keyframes.scss index a13891160a..372fb9e0ca 100644 --- a/cms/static/sass/_keyframes.scss +++ b/cms/static/sass/_keyframes.scss @@ -41,7 +41,29 @@ // ==================== -// notifications slide up +// notifications slide down +@mixin notificationsSlideDown { + 0% { + @include transform(translateY(-($notification-height*0.99))); + } + + 10% { + @include transform(translateY(-($notification-height))); + } + + 100% { + @include transform(translateY(0)); + } +} + +@-moz-keyframes notificationsSlideDown { @include notificationsSlideDown(); } +@-webkit-keyframes notificationsSlideDown { @include notificationsSlideDown(); } +@-o-keyframes notificationsSlideDown { @include notificationsSlideDown(); } +@keyframes notificationsSlideDown { @include notificationsSlideDown();} + +// ==================== + +// notifications slide up then down @mixin notificationsSlideUpDown { 0%, 100% { @include transform(translateY(0)); diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index b89fa7cc18..35f39fcd3a 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -16,7 +16,13 @@ $error-red: rgb(253, 87, 87); // colors - new for re-org $black: rgb(0,0,0); +$black-t1: rgba(0,0,0,0.25); +$black-t2: rgba(0,0,0,0.50); +$black-t3: rgba(0,0,0,0.75); $white: rgb(255,255,255); +$white-t1: rgba(255,255,255,0.25); +$white-t2: rgba(255,255,255,0.50); +$white-t3: rgba(255,255,255,0.75); $gray: rgb(127,127,127); $gray-l1: tint($gray,20%); diff --git a/cms/templates/alerts.html b/cms/templates/alerts.html index 3682cb3200..9b5ba70411 100644 --- a/cms/templates/alerts.html +++ b/cms/templates/alerts.html @@ -7,16 +7,46 @@ // notifications - demo $(document).ready(function() { - $('.test-notification').click(function(e) { + $('.hide-notification').click(function(e) { (e).preventDefault(); - $('.wrapper-notification').removeClass('is-shown'); - $(this.hash).toggleClass('is-shown'); + $('.wrapper-notification').removeClass('is-hiding is-shown'); + $(this.hash).addClass('is-hiding'); }); - $('.test-alert').click(function(e) { + $('.show-notification').click(function(e) { + (e).preventDefault(); + $('.wrapper-notification').removeClass('is-shown is-hiding'); + $(this.hash).addClass('is-shown'); + }); + + $('.show-notification-fleeting').click(function(e) { + (e).preventDefault(); + $('.wrapper-notification').removeClass('is-fleeting'); + $(this.hash).addClass('is-fleeting'); + }); + + $('.hide-alert').click(function(e) { + (e).preventDefault(); + $('.wrapper-alert').removeClass('is-hiding'); + $(this.hash).addClass('is-hiding'); + }); + + $('.show-alert').click(function(e) { (e).preventDefault(); $('.wrapper-alert').removeClass('is-shown'); - $(this.hash).toggleClass('is-shown'); + $(this.hash).addClass('is-shown'); + }); + + $('.hide-prompt').click(function(e) { + (e).preventDefault(); + $('.wrapper-prompt').removeClass('is-hiding is-shown'); + $(this.hash).addClass('is-hiding'); + }); + + $('.show-prompt').click(function(e) { + (e).preventDefault(); + $('.wrapper-prompt').removeClass('is-shown is-hiding'); + $(this.hash).addClass('is-shown'); }); }); @@ -243,16 +273,16 @@

    Note: alerts will probably never been shown based on click or page action and will primarily be loaded along with a pageload and initial display

    @@ -267,12 +297,46 @@

    Different Static Examples of Notifications

    + + +
    +
    +

    Prompts

    + presents a user with a choice, based on their previous interaction, that must be decided before they can proceed +
    + +

    In Studio, prompts are dialogs that are presented above all other page components and present a user with a choice, based on their previous interaction, that must be decided before they can proceed (or return to the previous interaction step).

    + +

    Different Static Examples of Notifications

    + +
    @@ -368,7 +432,7 @@
    -
    +
    @@ -394,4 +458,25 @@
    + + +
    +
    +
    +

    Are You Sure You Want to Do That?

    +
    + + +
    +
    \ No newline at end of file From 2ce4ecbc8481964e18d4cba475ba37b84f05a9db Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 1 Mar 2013 15:48:15 -0500 Subject: [PATCH 015/483] local merge with master - finished --- cms/templates/overview.html | 288 ++++++++++++++++-------------------- 1 file changed, 128 insertions(+), 160 deletions(-) diff --git a/cms/templates/overview.html b/cms/templates/overview.html index ec35e4864f..27fd208e0d 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -13,144 +13,145 @@ <%namespace name="units" file="widgets/units.html" /> <%block name="jsextra"> - - - - - - - - + + + + + + + + - + + <%block name="header_extras"> - + + + + - - - - +
      +
    1. + + New Unit + +
    2. +
    + + <%block name="content"> -
    -
    -

    Section Release Date

    -
    - - -
    -

    On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

    +
    +
    +

    Section Release Date

    +
    + + +
    +

    On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

    +
    + SaveCancel
    - SaveCancel
    -
    -
    -
    -
    - Course Content -

    Course Outline

    -
    +
    +
    +
    + Course Content +

    Course Outline

    +
    - -
    -
    + +
    +
    + +
    +
    +
    + % for section in sections: +
    +
    + -
    -
    -
    - % for section in sections: -
    -
    -

    ${section.display_name} @@ -211,44 +212,11 @@ $(document).ready(function(){ % endfor -

    -
    -
    - -
      - % for subsection in section.get_children(): - - % endfor -
    -
    -
    - % endfor -
    +
    + % endfor +
    +
    -
    -
    - +
    From bc0b1b09580a6b154ecc48ae1ba852cf0b41ff7e Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Sat, 2 Mar 2013 22:06:55 -0500 Subject: [PATCH 016/483] studio - alerts: cleaned up animation standards and made mixins to reference as well as got prompt UI pattern behavior working --- cms/static/sass/_alerts.scss | 30 ++++++++--- cms/static/sass/_keyframes.scss | 95 ++++++++++++++++++++++++++++++--- 2 files changed, 109 insertions(+), 16 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 9a2ebf1854..a152db636a 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -9,7 +9,7 @@ width: 100%; height: 100%; text-align: center; - z-index: 100; + z-index: 10000; &:before { content: ''; @@ -372,7 +372,8 @@ &.saving { .icon-saving { - @include animation(rotateClockwise 3.0s forwards linear infinite); + @include anim-rotateClockwise(3s, linear, infinite); + width: 22px; } @@ -661,13 +662,26 @@ .js { .wrapper-prompt { - @include transition (opacity 0.25s ease-in-out); display: none; - opacity: 0; + + .prompt { + @include anim-bounceIn(0.5s); + opacity: 0.1; + } &.is-shown { display: block; - opacity: 1.0; + + .prompt { + opacity: 1.0; + } + } + + &.is-hiding { + + .prompt { + @include anim-bounceOut(0.5s); + } } } @@ -684,15 +698,15 @@ // varying animations &.is-shown { - @include animation(notificationsSlideUp 1s forwards ease-in-out 1); + @include anim-notificationsSlideUp(1s); } &.is-hiding { - @include animation(notificationsSlideDown 1s forwards ease-in-out 1); + @include anim-notificationsSlideDown(1s); } &.is-fleeting { - @include animation(notificationsSlideUpDown 2s forwards ease-in-out 1); + @include anim-notificationsSlideUpDown(2s); } } } diff --git a/cms/static/sass/_keyframes.scss b/cms/static/sass/_keyframes.scss index 372fb9e0ca..817dc27132 100644 --- a/cms/static/sass/_keyframes.scss +++ b/cms/static/sass/_keyframes.scss @@ -17,6 +17,14 @@ @-o-keyframes rotateClockwise { @include rotateClockwise(); } @keyframes rotateClockwise { @include rotateClockwise();} +@mixin anim-rotateClockwise($duration, $timing: ease-in-out, $count: 1) { + @include animation-name(rotateClockwise); + @include animation-duration($duration); + @include animation-timing-function($timing); + @include animation-fill-mode(both); + @include animation-iteration-count($count); +} + // ==================== // notifications slide up @@ -39,6 +47,14 @@ @-o-keyframes notificationsSlideUp { @include notificationsSlideUp(); } @keyframes notificationsSlideUp { @include notificationsSlideUp();} +@mixin anim-notificationsSlideUp($duration, $timing: ease-in-out, $count: 1) { + @include animation-name(notificationsSlideUp); + @include animation-duration($duration); + @include animation-timing-function($timing); + @include animation-fill-mode(both); + @include animation-iteration-count($count); +} + // ==================== // notifications slide down @@ -61,6 +77,14 @@ @-o-keyframes notificationsSlideDown { @include notificationsSlideDown(); } @keyframes notificationsSlideDown { @include notificationsSlideDown();} +@mixin anim-notificationsSlideDown($duration, $timing: ease-in-out, $count: 1) { + @include animation-name(notificationsSlideDown); + @include animation-duration($duration); + @include animation-timing-function($timing); + @include animation-fill-mode(both); + @include animation-iteration-count($count); +} + // ==================== // notifications slide up then down @@ -83,13 +107,21 @@ @-o-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); } @keyframes notificationsSlideUpDown { @include notificationsSlideUpDown();} +@mixin anim-notificationsSlideUpDown($duration, $timing: ease-in-out, $count: 1) { + @include animation-name(notificationsSlideUpDown); + @include animation-duration($duration); + @include animation-timing-function($timing); + @include animation-fill-mode(both); + @include animation-iteration-count($count); +} + // ==================== // bounce in -@mixin bounce-in { +@mixin bounceIn { 0% { opacity: 0; - @include transform(scale(.3)); + @include transform(scale(0.3)); } 50% { @@ -102,14 +134,61 @@ } } -@-moz-keyframes bounce-in { @include bounce-in(); } -@-webkit-keyframes bounce-in { @include bounce-in(); } -@-o-keyframes bounce-in { @include bounce-in(); } -@keyframes bounce-in { @include bounce-in();} +@-moz-keyframes bounceIn { @include bounceIn(); } +@-webkit-keyframes bounceIn { @include bounceIn(); } +@-o-keyframes bounceIn { @include bounceIn(); } +@keyframes bounceIn { @include bounceIn();} -@mixin bounce-in-animation($duration, $timing: ease-in-out) { - @include animation-name(bounce-in); +@mixin anim-bounceIn($duration, $timing: ease-in-out, $count: 1) { + @include animation-name(bounceIn); @include animation-duration($duration); @include animation-timing-function($timing); @include animation-fill-mode(both); + @include animation-iteration-count($count); +} + +// ==================== + +// bounce in +@mixin bounceOut { + 0% { + opacity: 0; + @include transform(scale(0.3)); + } + + 50% { + opacity: 1; + @include transform(scale(1.05)); + } + + 100% { + @include transform(scale(1)); + } + + 0% { + @include transform(scale(1)); + } + + 50% { + opacity: 1; + @include transform(scale(1.05)); + } + + 100% { + opacity: 0; + @include transform(scale(0.3)); + } +} + +@-moz-keyframes bounceOut { @include bounceOut(); } +@-webkit-keyframes bounceOut { @include bounceOut(); } +@-o-keyframes bounceOut { @include bounceOut(); } +@keyframes bounceOut { @include bounceOut();} + +@mixin anim-bounceOut($duration, $timing: ease-in-out, $count: 1) { + @include animation-name(bounceOut); + @include animation-duration($duration); + @include animation-timing-function($timing); + @include animation-fill-mode(both); + @include animation-iteration-count($count); } \ No newline at end of file From 72c58cd454e65f282d0b7d93f0b454f73fdafdc8 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Sun, 3 Mar 2013 20:44:48 -0500 Subject: [PATCH 017/483] studio - alerts: refactored styles for common content types across UI, got prompts set up and styled, and refactored base template for future prompt/notification/alert use --- cms/static/js/base.js | 12 +- cms/static/sass/_alerts.scss | 296 +++++++++++++++----------------- cms/static/sass/_base.scss | 7 + cms/static/sass/_keyframes.scss | 32 ++-- cms/static/sass/_variables.scss | 2 + cms/templates/alerts.html | 295 ++++++++++++++++++------------- cms/templates/base.html | 39 +++-- 7 files changed, 375 insertions(+), 308 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 9af785045f..a6e8190e17 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -62,10 +62,16 @@ $(document).ready(function () { $(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding'); }); - // prompt close - $('.prompt .action-cancel').click(function(e) { + // prompt pop + $('.action-prompt').click(function(e){ (e).preventDefault(); - $(this).closest('.wrapper-prompt').removeClass('is-shown').addClass('is-hiding'); + $body.toggleClass('prompt-is-shown'); + }); + + // prompt close + $('.prompt .action-cancel, .prompt .action-proceed').click(function(e) { + (e).preventDefault(); + $body.removeClass('prompt-is-shown'); }); // nav - dropdown related diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index a152db636a..c507034284 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -1,11 +1,35 @@ // studio alerts and notifications // ==================== +// shared +.wrapper-notification, .wrapper-alert, .prompt { + @include box-sizing(border-box); + background: $gray-d3; + border-top: 4px solid $gray-d4; + + .copy { + @include font-size(13); + color: $gray-l2; + + .title { + color: $white; + } + + .nav-actions { + + .action-primary { + color: $gray-d4; + } + } + } +} + // prompts .wrapper-prompt { + @include transition(all 0.05s ease-in-out); position: fixed; top: 0; - background: $black-t1; + background: $black-t0; width: 100%; height: 100%; text-align: center; @@ -20,20 +44,80 @@ } .prompt { - @include border-radius(3px); - @include box-sizing(border-box); - @include box-shadow(0 0 4px $shadow-d1); + @include border-radius(($baseline/5)); + @include box-shadow(0 0 3px $shadow-d1); display: inline-block; vertical-align: middle; - width: $baseline*15; - padding: $baseline; - background: $white; + width: $baseline*17.5; + border: 4px solid $black; + text-align: left; + + .copy { + border-top: 4px solid $blue; + padding: $baseline; + } .nav-actions { + @include box-shadow(inset 0 1px 2px $shadow-d1); + border-top: 1px solid $black-t1; + padding: ($baseline*0.75) $baseline; + background: $gray-d4; .nav-item { + display: inline-block; + margin-right: ($baseline*0.75); + &:last-child { + margin-right: 0; + } } + + .action-primary { + @include blue-button(); + @include font-size(13); + border-color: $blue-d2; + font-weight: 600; + } + + .action-secondary { + @include font-size(13); + } + } + } + + // types of prompts - error + .prompt.error { + + .icon-error { + color: $red-l1; + } + + .copy { + border-top-color: $red-l1; + } + } + + // types of prompts - confirmation + .prompt.confirmation { + + .icon-error { + color: $green; + } + + .copy { + border-top-color: $green; + } + } + + // types of prompts - error + .prompt.warning { + + .icon-warning { + color: $orange; + } + + .copy { + border-top-color: $orange; } } } @@ -41,15 +125,12 @@ // notifications .wrapper-notification { @include clearfix(); - @include box-sizing(border-box); @include box-shadow(0 -1px 3px $shadow); position: fixed; bottom: 0; z-index: 1000; width: 100%; - border-top: 4px solid $gray-l1; padding: ($baseline*0.75) ($baseline*2); - background: $gray-d3; &.wrapper-notification-warning { border-top-color: $orange; @@ -67,7 +148,7 @@ } } - &.wrapper-cation-error { + &.wrapper-notification-error { border-top-color: $red-l1; .icon-error { @@ -208,29 +289,6 @@ } } - // with cancel - .action-notification-close { - @include border-top-radius(3px); - position: absolute; - top: -34px; - right: $baseline; - padding: ($baseline/5) ($baseline/2.5) 0 ($baseline/2.5); - background: $gray-l1; - text-align: center; - - .label { - @include text-sr(); - } - - .icon { - @include font-size(14); - color: $white; - width: auto; - margin: 0; - padding: 2px; - } - } - // with actions &.has-actions { @@ -272,108 +330,17 @@ } } - // notification types - &.warning { - - .action-notification-close { - background: $orange; - - &:hover { - background: $orange-s2; - } - } - - .action-primary { - @include orange-button(); - @include font-size(13); - border-color: $orange-d2; - } - - a { - color: $orange; - - &:hover { - color: $orange-s2; - } - } - } - - &.error { - - .action-notification-close { - background: $red-l1; - - &:hover { - background: $red; - } - } - - .action-primary { - @include red-button(); - @include font-size(13); - border-color: $red-d2; - } - - a { - color: $red-l1; - - &:hover { - color: $red; - } - } - } - &.confirmation { .copy { margin-top: ($baseline/5); } - - .action-notification-close { - background: $green; - - &:hover { - background: $green-s2; - } - } - - .action-primary { - @include green-button(); - @include font-size(13); - border-color: $green-d2; - } - - a { - color: $green; - - &:hover { - color: $green-s2; - } - } - } - - &.announcement { - - .action-notification-close { - background: $blue; - - &:hover { - background: $blue-s1; - } - } - - .action-primary { - @include blue-button(); - @include font-size(13); - border-color: $blue-d2; - } } &.saving { .icon-saving { @include anim-rotateClockwise(3s, linear, infinite); - width: 22px; } @@ -559,12 +526,12 @@ // with cancel .action-alert-close { - @include border-bottom-radius(3px); + @include border-bottom-radius(($baseline/5)); position: absolute; top: -($baseline/10); right: $baseline; padding: ($baseline/4) ($baseline/2) 0 ($baseline/2); - background: $gray-d1; + background: $gray-d4; text-align: center; .label { @@ -580,16 +547,20 @@ } &:hover { - background: $gray-l1; + background: $gray-d1; } } +} - // alert types +.alert, .notification, .prompt { + + // types - warning &.warning { - - .action-primary { + + .nav-actions .action-primary { @include orange-button(); border-color: $orange-d2; + color: $gray-d4; } a { @@ -601,9 +572,10 @@ } } + // types - error &.error { - .action-primary { + .nav-actions .action-primary { @include red-button(); border-color: $red-d2; } @@ -617,9 +589,27 @@ } } + // types - announcement + &.announcement { + + .nav-actions .action-primary { + @include blue-button(); + border-color: $blue-d2; + } + + a { + color: $blue; + + &:hover { + color: $blue-s2; + } + } + } + + // types - confirmation &.confirmation { - .action-primary { + .nav-actions .action-primary { @include green-button(); border-color: $green-d2; } @@ -633,17 +623,10 @@ } } - &.announcement { - - .action-primary { - @include blue-button(); - border-color: $blue-d2; - } - } - + // types - step required &.step-required { - .action-primary { + .nav-actions .action-primary { border-color: $pink-d2; @include pink-button(); } @@ -661,28 +644,33 @@ // js enabled .js { + // prompt set-up .wrapper-prompt { - display: none; + visibility: hidden; + pointer-events: none; .prompt { - @include anim-bounceIn(0.5s); - opacity: 0.1; + opacity: 0; + } + } + + // prompt showing + &.prompt-is-shown { + + .wrapper-view { + -webkit-filter: blur(2px) grayscale(25%); + filter: blur(2px) grayscale(25%); } - &.is-shown { - display: block; + .wrapper-prompt.is-shown { + visibility: visible; + pointer-events: auto; .prompt { + @include anim-bounceIn(0.5s); opacity: 1.0; } } - - &.is-hiding { - - .prompt { - @include anim-bounceOut(0.5s); - } - } } .wrapper-alert { diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index d5a8adc6cb..5fd05d27a6 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -103,6 +103,13 @@ p, ul, ol, dl { // ==================== +// layout - basic +.wrapper-view { + +} + +// ==================== + // layout - basic page header .wrapper-mast { margin: 0; diff --git a/cms/static/sass/_keyframes.scss b/cms/static/sass/_keyframes.scss index 817dc27132..a756f66b2e 100644 --- a/cms/static/sass/_keyframes.scss +++ b/cms/static/sass/_keyframes.scss @@ -17,12 +17,14 @@ @-o-keyframes rotateClockwise { @include rotateClockwise(); } @keyframes rotateClockwise { @include rotateClockwise();} -@mixin anim-rotateClockwise($duration, $timing: ease-in-out, $count: 1) { +@mixin anim-rotateClockwise($duration, $timing: ease-in-out, $count: 1, $delay: 0) { @include animation-name(rotateClockwise); @include animation-duration($duration); + @include animation-delay($delay); @include animation-timing-function($timing); - @include animation-fill-mode(both); @include animation-iteration-count($count); + @include animation-fill-mode(both); + } // ==================== @@ -47,12 +49,14 @@ @-o-keyframes notificationsSlideUp { @include notificationsSlideUp(); } @keyframes notificationsSlideUp { @include notificationsSlideUp();} -@mixin anim-notificationsSlideUp($duration, $timing: ease-in-out, $count: 1) { +@mixin anim-notificationsSlideUp($duration, $timing: ease-in-out, $count: 1, $delay: 0) { @include animation-name(notificationsSlideUp); @include animation-duration($duration); + @include animation-delay($delay); @include animation-timing-function($timing); - @include animation-fill-mode(both); @include animation-iteration-count($count); + @include animation-fill-mode(both); + } // ==================== @@ -77,12 +81,13 @@ @-o-keyframes notificationsSlideDown { @include notificationsSlideDown(); } @keyframes notificationsSlideDown { @include notificationsSlideDown();} -@mixin anim-notificationsSlideDown($duration, $timing: ease-in-out, $count: 1) { +@mixin anim-notificationsSlideDown($duration, $timing: ease-in-out, $count: 1, $delay: 0) { @include animation-name(notificationsSlideDown); @include animation-duration($duration); + @include animation-delay($delay); @include animation-timing-function($timing); - @include animation-fill-mode(both); @include animation-iteration-count($count); + @include animation-fill-mode(both); } // ==================== @@ -107,12 +112,13 @@ @-o-keyframes notificationsSlideUpDown { @include notificationsSlideUpDown(); } @keyframes notificationsSlideUpDown { @include notificationsSlideUpDown();} -@mixin anim-notificationsSlideUpDown($duration, $timing: ease-in-out, $count: 1) { +@mixin anim-notificationsSlideUpDown($duration, $timing: ease-in-out, $count: 1, $delay: 0) { @include animation-name(notificationsSlideUpDown); @include animation-duration($duration); + @include animation-delay($delay); @include animation-timing-function($timing); - @include animation-fill-mode(both); @include animation-iteration-count($count); + @include animation-fill-mode(both); } // ==================== @@ -139,12 +145,13 @@ @-o-keyframes bounceIn { @include bounceIn(); } @keyframes bounceIn { @include bounceIn();} -@mixin anim-bounceIn($duration, $timing: ease-in-out, $count: 1) { +@mixin anim-bounceIn($duration, $timing: ease-in-out, $count: 1, $delay: 0) { @include animation-name(bounceIn); @include animation-duration($duration); + @include animation-delay($delay); @include animation-timing-function($timing); - @include animation-fill-mode(both); @include animation-iteration-count($count); + @include animation-fill-mode(both); } // ==================== @@ -185,10 +192,11 @@ @-o-keyframes bounceOut { @include bounceOut(); } @keyframes bounceOut { @include bounceOut();} -@mixin anim-bounceOut($duration, $timing: ease-in-out, $count: 1) { +@mixin anim-bounceOut($duration, $timing: ease-in-out, $count: 1, $delay: 0) { @include animation-name(bounceOut); @include animation-duration($duration); + @include animation-delay($delay); @include animation-timing-function($timing); - @include animation-fill-mode(both); @include animation-iteration-count($count); + @include animation-fill-mode(both); } \ No newline at end of file diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index 35f39fcd3a..7998d0b199 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -16,10 +16,12 @@ $error-red: rgb(253, 87, 87); // colors - new for re-org $black: rgb(0,0,0); +$black-t0: rgba(0,0,0,0.125); $black-t1: rgba(0,0,0,0.25); $black-t2: rgba(0,0,0,0.50); $black-t3: rgba(0,0,0,0.75); $white: rgb(255,255,255); +$white-t0: rgba(255,255,255,0.125); $white-t1: rgba(255,255,255,0.25); $white-t2: rgba(255,255,255,0.50); $white-t3: rgba(255,255,255,0.75); diff --git a/cms/templates/alerts.html b/cms/templates/alerts.html index 9b5ba70411..12c605d863 100644 --- a/cms/templates/alerts.html +++ b/cms/templates/alerts.html @@ -37,15 +37,15 @@ $(this.hash).addClass('is-shown'); }); - $('.hide-prompt').click(function(e) { - (e).preventDefault(); - $('.wrapper-prompt').removeClass('is-hiding is-shown'); - $(this.hash).addClass('is-hiding'); + $('.hide-prompt').click(function(e){ + (e).preventDefault(); + $body.removeClass('prompt-is-shown'); }); $('.show-prompt').click(function(e) { - (e).preventDefault(); - $('.wrapper-prompt').removeClass('is-shown is-hiding'); + (e).preventDefault(); + $body.toggleClass('prompt-is-shown'); + $('.wrapper-prompt').removeClass('is-shown'); $(this.hash).addClass('is-shown'); }); }); @@ -53,6 +53,122 @@ <%block name="content"> +
    +
    +

    Section Release Date

    +
    + + +
    +

    On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

    +
    +
    + SaveCancel +
    +
    + +
    +
    +
    + UX Design +

    Alerts & Notifications

    +
    +
    +
    + +
    +
    +
    +
    +
    +

    Alerts

    + persistant, static messages to the user +
    + +

    In Studio, alerts are 1) general warnings/notes (e.g. drafts, published content, next steps) about the current view a user is interacting with or 2) notes about the status (e.g. saved confirmations, errors, next system steps) of any previous state that need to communicated to the user when arriving at the current view.

    + +

    Different Static Examples of Alerts

    +

    Note: alerts will probably never been shown based on click or page action and will primarily be loaded along with a pageload and initial display

    + + +
    + +
    +
    +

    Notifications

    + contextual, feedback-based, and temporal messages to the user +
    + +

    In Studio, notifications are meant to inform the user of 1) any system status (e.g. saving, processing/validating) occurring based on any action they have taken or 2) any decisions (e.g. saving/discarding) a user must make to confirm.

    + +

    Different Static Examples of Notifications

    + + +
    + +
    +
    +

    Prompts

    + presents a user with a choice, based on their previous interaction, that must be decided before they can proceed +
    + +

    In Studio, prompts are dialogs that are presented above all other page components and present a user with a choice, based on their previous interaction, that must be decided before they can proceed (or return to the previous interaction step).

    + +

    Different Static Examples of Prompts

    + + +
    +
    +
    +
    + + +<%block name="view_alerts">
    @@ -234,115 +350,9 @@
    + -
    -
    -

    Section Release Date

    -
    - - -
    -

    On the date set above, this section – – will be released to students. Any units marked private will only be visible to admins.

    -
    -
    - SaveCancel -
    -
    - -
    -
    -
    - UX Design -

    Alerts & Notifications

    -
    -
    -
    - -
    -
    -
    -
    -
    -

    Alerts

    - persistant, static messages to the user -
    - -

    In Studio, alerts are 1) general warnings/notes (e.g. drafts, published content, next steps) about the current view a user is interacting with or 2) notes about the status (e.g. saved confirmations, errors, next system steps) of any previous state that need to communicated to the user when arriving at the current view.

    - -

    Different Static Examples of Alerts

    -

    Note: alerts will probably never been shown based on click or page action and will primarily be loaded along with a pageload and initial display

    - - -
    - -
    -
    -

    Notifications

    - contextual, feedback-based, and temporal messages to the user -
    - -

    In Studio, notifications are meant to inform the user of 1) any system status (e.g. saving, processing/validating) occurring based on any action they have taken or 2) any decisions (e.g. saving/discarding) a user must make to confirm.

    - -

    Different Static Examples of Notifications

    - - -
    - -
    -
    -

    Prompts

    - presents a user with a choice, based on their previous interaction, that must be decided before they can proceed -
    - -

    In Studio, prompts are dialogs that are presented above all other page components and present a user with a choice, based on their previous interaction, that must be decided before they can proceed (or return to the previous interaction step).

    - -

    Different Static Examples of Notifications

    - - -
    -
    -
    -
    - +<%block name="view_notifications">
    @@ -388,11 +398,6 @@ - - - - close notification -
    @@ -458,22 +463,66 @@
    + - +<%block name="view_prompts"> +
    -

    Are You Sure You Want to Do That?

    +

    Delete "Introduction & Overview"?

    +

    Deleting a section cannot be undone and its contents cannot be recovered.

    +
    +
    + + +
    +
    +
    +

    Use Advanced Problem Editor?

    +

    If you proceed, you cannot edit this problem using the simple problem editor.

    +
    + + +
    +
    + + +
    +
    +
    +

    There Were Errors in Your Submission

    +

    Please correct the errors noted on the page and try again.

    +
    + + diff --git a/cms/templates/base.html b/cms/templates/base.html index 498897bd11..387b45a0d1 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -6,12 +6,12 @@ - <%block name="title"></%block> | - % if context_course: - <% ctx_loc = context_course.location %> - ${context_course.display_name} | - % endif - edX Studio + <%block name="title"></%block> | + % if context_course: + <% ctx_loc = context_course.location %> + ${context_course.display_name} | + % endif + edX Studio @@ -22,14 +22,13 @@ - + <%block name="header_extras"> - <%include file="widgets/header.html" /> - <%include file="courseware_vendor_js.html"/> + <%include file="courseware_vendor_js.html"/> @@ -48,15 +47,23 @@ - <%block name="content"> - <%include file="widgets/footer.html" /> + +
    + <%include file="widgets/header.html" /> + + <%block name="view_alerts"> + + <%block name="content"> + <%include file="widgets/footer.html" /> + <%block name="view_notifications"> +
    + + <%block name="view_prompts"> <%block name="jsextra"> - - - + \ No newline at end of file From 66657db0bee2e26a3966e6ca04b2483f88918754 Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Mon, 4 Mar 2013 09:49:41 -0700 Subject: [PATCH 018/483] Added support for superscripts in variables and fixed bug with normal subscripted variables raised to powers --- lms/lib/symmath/formula.py | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/lms/lib/symmath/formula.py b/lms/lib/symmath/formula.py index c34156da52..db74d5b271 100644 --- a/lms/lib/symmath/formula.py +++ b/lms/lib/symmath/formula.py @@ -247,6 +247,65 @@ class formula(object): fix_hat(k) fix_hat(xml) + def flatten_pmathml(xml): + ''' + Give the text version of PMathML elements + ''' + tag = gettag(xml) + if tag == 'mn': return xml.text + elif tag == 'mi': return xml.text + # elif tag == 'msub': return '_'.join([flatten_pmathml(y) for y in xml]) + # elif tag == 'msup': return '^'.join([flatten_pmathml(y) for y in xml]) + elif tag == 'mrow': return ''.join([flatten_pmathml(y) for y in xml]) + raise Exception, '[flatten_pmathml] unknown tag %s' % tag + + # find "tagged" superscripts + # they have the character \u200b in the superscript + # replace them with a__b so snuggle doesn't get confused + def fix_superscripts(xml): + for k in xml: + tag = gettag(k) + + # match node to a superscript + if (tag == 'msup' and + len(k) == 2 and gettag(k[1]) == 'mrow' and + gettag(k[1][0]) == 'mo' and k[1][0].text == u'\u200b'): # whew + + k[1].remove(k[1][0]) + newk = etree.Element('mi') + newk.text = '%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1])) + xml.replace(k, newk) + + if (tag == 'msubsup' and + len(k) == 3 and gettag(k[2]) == 'mrow' and + gettag(k[2][0]) == 'mo' and k[2][0].text == u'\u200b'): # whew + + k[2].remove(k[2][0]) + newk = etree.Element('mi') + newk.text = '%s_%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1]), flatten_pmathml(k[2])) + xml.replace(k, newk) + + fix_superscripts(k) + fix_superscripts(xml) + + # Snuggle returns an error when it sees an + # replace such elements with an , except the first element is of + # the form a_b. I.e. map a_b^c => (a_b)^c + def fix_msubsup(parent): + for child in parent: + # fix msubsup + if (gettag(child) == 'msubsup' and len(child) == 3): + newchild = etree.Element('msup') + newbase = etree.Element('mi') + newbase.text = '%s_%s' % (flatten_pmathml(child[0]), flatten_pmathml(child[1])) + newexp = child[2] + newchild.append(newbase) + newchild.append(newexp) + parent.replace(child, newchild) + + fix_msubsup(child) + fix_msubsup(xml) + self.xml = xml return self.xml @@ -257,6 +316,7 @@ class formula(object): try: xml = self.preprocess_pmathml(self.expr) except Exception, err: + # print 'Err %s while preprocessing; expr=%s' % (err, self.expr) return "Error! Cannot process pmathml" pmathml = etree.tostring(xml, pretty_print=True) self.the_pmathml = pmathml From eb1658730a6d258286e3d2134941015de91d8e2f Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 4 Mar 2013 12:30:04 -0500 Subject: [PATCH 019/483] studio - alerts: revised border styling on all elements and button/color inheritance --- cms/static/sass/_alerts.scss | 300 +++++++++++++++-------------------- 1 file changed, 126 insertions(+), 174 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index c507034284..a6ce1f9830 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -24,6 +24,112 @@ } } +.alert, .notification, .prompt { + + // types - confirm + &.confirm { + + .nav-actions .action-primary { + @include blue-button(); + border-color: $blue-d2; + } + + a { + color: $blue; + + &:hover { + color: $blue-s2; + } + } + } + + // types - warning + &.warning { + + .nav-actions .action-primary { + @include orange-button(); + border-color: $orange-d2; + color: $gray-d4; + } + + a { + color: $orange; + + &:hover { + color: $orange-s2; + } + } + } + + // types - error + &.error { + + .nav-actions .action-primary { + @include red-button(); + border-color: $red-d2; + } + + a { + color: $red-l1; + + &:hover { + color: $red; + } + } + } + + // types - announcement + &.announcement { + + .nav-actions .action-primary { + @include blue-button(); + border-color: $blue-d2; + } + + a { + color: $blue; + + &:hover { + color: $blue-s2; + } + } + } + + // types - confirmation + &.confirmation { + + .nav-actions .action-primary { + @include green-button(); + border-color: $green-d2; + } + + a { + color: $green; + + &:hover { + color: $green-s2; + } + } + } + + // types - step required + &.step-required { + + .nav-actions .action-primary { + border-color: $pink-d2; + @include pink-button(); + } + + a { + color: $pink; + + &:hover { + color: $pink-s1; + } + } + } +} + // prompts .wrapper-prompt { @include transition(all 0.05s ease-in-out); @@ -73,9 +179,7 @@ } .action-primary { - @include blue-button(); @include font-size(13); - border-color: $blue-d2; font-weight: 600; } @@ -125,73 +229,48 @@ // notifications .wrapper-notification { @include clearfix(); - @include box-shadow(0 -1px 3px $shadow); + @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $blue); position: fixed; bottom: 0; z-index: 1000; width: 100%; - padding: ($baseline*0.75) ($baseline*2); + border-top: 4px solid $black; + padding: $baseline ($baseline*2); &.wrapper-notification-warning { - border-top-color: $orange; + @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $orange); .icon-warning { color: $orange; } - - &:hover { - border-top-color: $orange-s2; - - .icon-warning { - color: $orange-s2; - } - } } &.wrapper-notification-error { - border-top-color: $red-l1; + @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $red-l1); .icon-error { color: $red-l1; } - - &:hover { - border-top-color: $red; - - .icon-error { - color: $red; - } - } } &.wrapper-notification-confirmation { - border-top-color: $green; + @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $green); .icon-confirmation { color: $green; } - - &:hover { - border-top-color: $green-s1; - - .icon-confirmation { - color: $green-s1; - } - } } &.wrapper-notification-saving { - border-top-color: $pink; - - &:hover { - border-top-color: $pink-s1; - } + @include box-shadow(0 -1px 3px $shadow, inset 0 3px 1px $pink); } // shorter/status notifications &.wrapper-notification-status { + @include border-top-radius(3px); width: ($baseline*8); right: ($baseline); + border: 4px solid $black; padding: ($baseline/2) $baseline; .notification { @@ -222,8 +301,10 @@ // help notifications &.wrapper-notification-help { + @include border-top-radius(3px); width: ($baseline*14); right: ($baseline); + border: 4px solid $black; padding: ($baseline/2) $baseline; .notification { @@ -304,7 +385,7 @@ .nav-actions { width: flex-grid(4, 12); float: right; - margin-top: ($baseline/2); + margin-top: ($baseline/4); text-align: right; .nav-item { @@ -355,95 +436,55 @@ // alerts .wrapper-alert { @include box-sizing(border-box); - @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1); + @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue); position: relative; top: -($baseline*1.5); z-index: 100; overflow: hidden; width: 100%; - border-bottom: 4px solid $gray-l1; + border-bottom: 2px solid $black; border-top: 1px solid $black; - padding: $baseline ($baseline*2); + padding: $baseline ($baseline*2) ($baseline*1.5) ($baseline*2); background: $gray-d3; &.wrapper-alert-warning { - border-bottom-color: $orange; + @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $orange); .icon-warning { color: $orange; } - - &:hover { - border-bottom-color: $orange-s2; - - .icon-warning { - color: $orange-s2; - } - } } &.wrapper-alert-error { - border-bottom-color: $red-l1; + @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $red-l1); .icon-error { color: $red-l1; } - - &:hover { - border-bottom-color: $red; - - .icon-error { - color: $red; - } - } } &.wrapper-alert-confirmation { - border-bottom-color: $green; + @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $green); .icon-confirmation { color: $green; } - - &:hover { - border-bottom-color: $green-s2; - - .icon-confirmation { - color: $green-s2; - } - } } &.wrapper-alert-announcement { - border-bottom-color: $blue; + @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue); .icon-announcement { color: $blue; } - - &:hover { - border-bottom-color: $blue-s2; - - .icon-announcement { - color: $blue-s2; - } - } } &.wrapper-alert-step-required { - border-bottom-color: $pink; + @include box-shadow(0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $pink); .icon-step-required { color: $pink; } - - &:hover { - border-bottom-color: $pink-s2; - - .icon-announcement { - color: $pink-s2; - } - } } } @@ -552,95 +593,6 @@ } } -.alert, .notification, .prompt { - - // types - warning - &.warning { - - .nav-actions .action-primary { - @include orange-button(); - border-color: $orange-d2; - color: $gray-d4; - } - - a { - color: $orange; - - &:hover { - color: $orange-s2; - } - } - } - - // types - error - &.error { - - .nav-actions .action-primary { - @include red-button(); - border-color: $red-d2; - } - - a { - color: $red-l1; - - &:hover { - color: $red; - } - } - } - - // types - announcement - &.announcement { - - .nav-actions .action-primary { - @include blue-button(); - border-color: $blue-d2; - } - - a { - color: $blue; - - &:hover { - color: $blue-s2; - } - } - } - - // types - confirmation - &.confirmation { - - .nav-actions .action-primary { - @include green-button(); - border-color: $green-d2; - } - - a { - color: $green; - - &:hover { - color: $green-s2; - } - } - } - - // types - step required - &.step-required { - - .nav-actions .action-primary { - border-color: $pink-d2; - @include pink-button(); - } - - a { - color: $pink; - - &:hover { - color: $pink-s1; - } - } - } -} - // js enabled .js { From ec90d349e20ed76b2b53e4281aeb5e7ad70727fc Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 4 Mar 2013 12:47:07 -0500 Subject: [PATCH 020/483] studio - alerts: revisited advanced editor notification UI to marry new styles/behavior - WIP --- cms/static/js/base.js | 2 +- cms/static/js/views/settings/advanced_view.js | 4 ++-- cms/templates/settings_advanced.html | 23 +++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index a6e8190e17..3cb1829ffb 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -57,7 +57,7 @@ $(document).ready(function () { }); // alert and notifications - manual & action-based close - $('.action-notification-close, .notification.has-actions .nav-actions a').click(function(e) { + $('.action-notification-close').click(function(e) { (e).preventDefault(); $(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding'); }); diff --git a/cms/static/js/views/settings/advanced_view.js b/cms/static/js/views/settings/advanced_view.js index d20a21f7e7..dc9adb30ed 100644 --- a/cms/static/js/views/settings/advanced_view.js +++ b/cms/static/js/views/settings/advanced_view.js @@ -141,13 +141,13 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ event.keyCode === 8 || event.keyCode === 46)) return; } this.$el.find(".message-status").removeClass("is-shown"); - $('.wrapper-notification').addClass('is-shown'); + $('.wrapper-notification').removeClass('is-hiding').addClass('is-shown'); this.buttonsVisible = true; } }, hideSaveCancelButtons: function() { - $('.wrapper-notification').removeClass('is-shown'); + $('.wrapper-notification').removeClass('is-shown').addClass('is-hiding'); this.buttonsVisible = false; }, diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html index ceee406398..f04015a4a9 100644 --- a/cms/templates/settings_advanced.html +++ b/cms/templates/settings_advanced.html @@ -105,20 +105,25 @@ editor.render();
    -
    +
    + +
    - - -

    Note: Your changes will not take effect until you save your - progress. Take care with key and value formatting, as validation is not implemented.

    +

    You've Made Some Changes

    +

    Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.

    -
    +
    +
    \ No newline at end of file From cbdf9ea25bc9ffd43ffd6213a0e20cdfb6670364 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 5 Mar 2013 09:57:14 -0500 Subject: [PATCH 021/483] studio - alerts: proofing older, but still needed in page alerts (unit drafts) styling --- cms/static/sass/_alerts.scss | 117 +++++++++++++++++++---------------- cms/templates/unit.html | 4 +- 2 files changed, 64 insertions(+), 57 deletions(-) diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index a6ce1f9830..395519f867 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -271,6 +271,7 @@ width: ($baseline*8); right: ($baseline); border: 4px solid $black; + border-bottom: none; padding: ($baseline/2) $baseline; .notification { @@ -305,6 +306,7 @@ width: ($baseline*14); right: ($baseline); border: 4px solid $black; + border-bottom: none; padding: ($baseline/2) $baseline; .notification { @@ -690,69 +692,74 @@ body.uxdesign.alerts { } } +// ==================== + // artifact styles -// .alert { -// padding: 15px 20px; -// margin-bottom: 30px; -// border-radius: 3px; -// border: 1px solid #edbd3c; -// border-radius: 3px; -// background: #fbf6e1; -// // background: #edbd3c; -// font-size: 14px; -// @include clearfix; +.main-wrapper { + .alert { + padding: 15px 20px; + margin-bottom: 30px; + border-radius: 3px; + border: 1px solid #edbd3c; + border-radius: 3px; + background: #fbf6e1; + // background: #edbd3c; + font-size: 14px; + @include clearfix; -// .alert-message { -// float: left; -// margin-top: 4px; -// } + .alert-message { + float: left; + margin: 4px 0 0 0; + color: $gray-d3; + } -// strong { -// font-weight: 700; -// } + strong { + font-weight: 700; + } -// .alert-action { -// float: right; + .alert-action { + float: right; -// &.secondary { -// @include orange-button; -// } -// } -// } + &.secondary { + @include orange-button; + } + } + } +} -// body.error { -// background: $darkGrey; -// color: #3c3c3c; +body.error { + background: $gray-d4; + color: $gray-d3; -// .primary-header { -// display: none; -// } + .primary-header { + display: none; + } -// .error-prompt { -// width: 700px; -// margin: 150px auto; -// padding: 60px 50px 90px; -// border-radius: 3px; -// background: #fff; -// text-align: center; -// } + .error-prompt { + width: 700px; + margin: 150px auto; + padding: 60px 50px 90px; + border-radius: 3px; + background: $white; + text-align: center; + } -// h1 { -// float: none; -// margin: 0; -// font-size: 60px; -// font-weight: 300; -// color: #3c3c3c; -// } + h1 { + float: none; + margin: 0; + font-size: 60px; + font-weight: 300; + color: $gray-d3; + } -// .description { -// margin-bottom: 50px; -// font-size: 21px; -// } + .description { + margin-bottom: 50px; + font-size: 21px; + } -// .back-button { -// @include blue-button; -// padding: 14px 40px 18px; -// font-size: 18px; -// } -// } + .back-button { + @include blue-button; + padding: 14px 40px 18px; + font-size: 18px; + } +} diff --git a/cms/templates/unit.html b/cms/templates/unit.html index fa4b5dc20b..63d04a837f 100644 --- a/cms/templates/unit.html +++ b/cms/templates/unit.html @@ -33,7 +33,7 @@ <%block name="content">
    -
    +

    You are editing a draft. % if published_date: This unit was originally published on ${published_date}. @@ -143,7 +143,7 @@

    -
    +

    This unit has been published. To make changes, you must edit a draft.

    This is a draft of the published unit. To update the live version, you must replace it with this draft.

    From c6545eb092d7bcbe2d934ac2753d6fb8113f0468 Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Wed, 6 Mar 2013 06:21:08 -0700 Subject: [PATCH 022/483] Begin to document symmath as we go --- .../js/capa/symbolic_mathjax_preprocessor.js | 22 +++++++ .../course_data_formats/symbolic_response.rst | 26 ++++++++ lms/lib/symmath/formula.py | 59 +++++++++++++++++-- 3 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 common/static/js/capa/symbolic_mathjax_preprocessor.js create mode 100644 doc/public/course_data_formats/symbolic_response.rst diff --git a/common/static/js/capa/symbolic_mathjax_preprocessor.js b/common/static/js/capa/symbolic_mathjax_preprocessor.js new file mode 100644 index 0000000000..19104553dc --- /dev/null +++ b/common/static/js/capa/symbolic_mathjax_preprocessor.js @@ -0,0 +1,22 @@ +window.SymbolicMathjaxPreprocessor = function () { + this.fn = function (eqn) { + // flags and config + var superscriptsOn = true; + + if (superscriptsOn) { + // find instances of "__" and make them superscripts ("^") and tag them + // as such. Specifcally replace instances of "__X" or "__{XYZ}" with + // "^{CHAR$1}", marking superscripts as different from powers + + // a zero width space--this is an invisible character that no one would + // use, that gets passed through MathJax and to the server + var c = "\u200b"; + eqn = eqn.replace(/__(?:([^\{])|\{([^\}]+)\})/g, '^{' + c + '$1$2}'); + + // NOTE: MathJax supports '\class{name}{mathcode}' but not for asciimath + // input, which is too bad. This would be preferable to the char tag + } + + return eqn; + }; +}; diff --git a/doc/public/course_data_formats/symbolic_response.rst b/doc/public/course_data_formats/symbolic_response.rst new file mode 100644 index 0000000000..773821766e --- /dev/null +++ b/doc/public/course_data_formats/symbolic_response.rst @@ -0,0 +1,26 @@ +################# +Symbolic Response +################# + +This document plans to document features that the current symbolic response +supports. In general it allows the input and validation of math expressions, +up to commutativity and some identities. + + +******** +Features +******** + +This is a partial list of features, to be revised as we go along: + * sub and superscripts: an expression following the ``^`` character + indicates exponentiation. To use superscripts in variables, the syntax + is ``b_x__d`` for the variable ``b`` with subscript ``x`` and super + ``d``. + + An example of a problem:: + + + + + + It's a bit of a pain to enter that. diff --git a/lms/lib/symmath/formula.py b/lms/lib/symmath/formula.py index 7c4ea084d6..914a65d1b0 100644 --- a/lms/lib/symmath/formula.py +++ b/lms/lib/symmath/formula.py @@ -248,14 +248,21 @@ class formula(object): fix_hat(xml) def flatten_pmathml(xml): - ''' - Give the text version of PMathML elements + ''' Give the text version of certain PMathML elements + + Sometimes MathML will be given with each letter separated (it + doesn't know if its implicit multiplication or what). From an xml + node, find the (text only) variable name it represents. So it takes + + m + a + x + + and returns 'max', for easier use later on. ''' tag = gettag(xml) if tag == 'mn': return xml.text elif tag == 'mi': return xml.text - # elif tag == 'msub': return '_'.join([flatten_pmathml(y) for y in xml]) - # elif tag == 'msup': return '^'.join([flatten_pmathml(y) for y in xml]) elif tag == 'mrow': return ''.join([flatten_pmathml(y) for y in xml]) raise Exception, '[flatten_pmathml] unknown tag %s' % tag @@ -263,23 +270,63 @@ class formula(object): # they have the character \u200b in the superscript # replace them with a__b so snuggle doesn't get confused def fix_superscripts(xml): + ''' Look for and replace sup elements with 'X__Y' or 'X_Y__Z' + + In the javascript, variables with '__X' in them had an invisible + character inserted into the sup (to distinguish from powers) + E.g. normal: + + a + b + c + + to be interpreted '(a_b)^c' (nothing done by this method) + + And modified: + + b + x + + + d + + + to be interpreted 'a_b__c' + + also: + + x + + + B + + + to be 'x__B' + ''' for k in xml: tag = gettag(k) - # match node to a superscript + # match things like the last example-- + # the second item in msub is an mrow with the first + # character equal to \u200b if (tag == 'msup' and len(k) == 2 and gettag(k[1]) == 'mrow' and gettag(k[1][0]) == 'mo' and k[1][0].text == u'\u200b'): # whew + # replace the msup with 'X__Y' k[1].remove(k[1][0]) newk = etree.Element('mi') newk.text = '%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1])) xml.replace(k, newk) + # match things like the middle example- + # the third item in msubsup is an mrow with the first + # character equal to \u200b if (tag == 'msubsup' and len(k) == 3 and gettag(k[2]) == 'mrow' and gettag(k[2][0]) == 'mo' and k[2][0].text == u'\u200b'): # whew + # replace the msubsup with 'X_Y__Z' k[2].remove(k[2][0]) newk = etree.Element('mi') newk.text = '%s_%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1]), flatten_pmathml(k[2])) @@ -316,7 +363,7 @@ class formula(object): try: xml = self.preprocess_pmathml(self.expr) except Exception, err: - # print 'Err %s while preprocessing; expr=%s' % (err, self.expr) + log.warning('Err %s while preprocessing; expr=%s' % (err, self.expr)) return "Error! Cannot process pmathml" pmathml = etree.tostring(xml, pretty_print=True) self.the_pmathml = pmathml From 62514d85a37fdfa5e60fa676902554e9c8eb1b19 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 6 Mar 2013 15:36:38 -0500 Subject: [PATCH 023/483] studio - alerts: loosely wired advanced settings with new notification and alert messages --- cms/static/js/views/settings/advanced_view.js | 6 ++-- cms/static/sass/_alerts.scss | 2 +- cms/templates/settings_advanced.html | 33 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/cms/static/js/views/settings/advanced_view.js b/cms/static/js/views/settings/advanced_view.js index dc9adb30ed..1aaa7d0c1e 100644 --- a/cms/static/js/views/settings/advanced_view.js +++ b/cms/static/js/views/settings/advanced_view.js @@ -114,13 +114,13 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ }, showMessage: function (type) { - this.$el.find(".message-status").removeClass("is-shown"); + $(".wrapper-alert").removeClass("is-shown"); if (type) { if (type === this.error_saving) { - this.$el.find(".message-status.error").addClass("is-shown"); + $(".wrapper-alert-error").addClass("is-shown"); } else if (type === this.successful_changes) { - this.$el.find(".message-status.confirm").addClass("is-shown"); + $(".wrapper-alert-confirmation").addClass("is-shown"); this.hideSaveCancelButtons(); } } diff --git a/cms/static/sass/_alerts.scss b/cms/static/sass/_alerts.scss index 395519f867..07ac1e7913 100644 --- a/cms/static/sass/_alerts.scss +++ b/cms/static/sass/_alerts.scss @@ -234,7 +234,7 @@ bottom: 0; z-index: 1000; width: 100%; - border-top: 4px solid $black; + border-width: 2px; padding: $baseline ($baseline*2); &.wrapper-notification-warning { diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html index f04015a4a9..00325f01b4 100644 --- a/cms/templates/settings_advanced.html +++ b/cms/templates/settings_advanced.html @@ -102,7 +102,9 @@ editor.render();
    + +<%block name="view_notifications">
    @@ -126,4 +128,35 @@ editor.render();
    + + +<%block name="view_alerts"> + +
    +
    + + +
    +

    Your policy changes have been saved.

    +

    Please note that validation of your policy key and value pairs is not currently in place yet. If you are having difficulties, please review your policy pairs.

    +
    + + + + close alert + +
    +
    + + +
    +
    + + +
    +

    There was an error saving your information

    +

    Please see the error below and correct it to ensure there are no problems in rendering your course.

    +
    +
    +
    \ No newline at end of file From 559a311acbc093ded0a7da9d0d57e90a2b0687d3 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 7 Mar 2013 11:37:15 -0500 Subject: [PATCH 024/483] studio - alerts: refactored static alerts demo view to have a different template/view/url name to help with user facing views vs. documentation views --- cms/djangoapps/contentstore/views.py | 4 ++-- cms/templates/{alerts.html => ux-alerts.html} | 0 cms/urls.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename cms/templates/{alerts.html => ux-alerts.html} (100%) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index df40a1972d..464342f3e1 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -110,8 +110,8 @@ def howitworks(request): else: return render_to_response('howitworks.html', {}) -def alerts(request): - return render_to_response('alerts.html', {}) +def ux_alerts(request): + return render_to_response('ux-alerts.html', {}) # ==== Views for any logged-in user ================================== diff --git a/cms/templates/alerts.html b/cms/templates/ux-alerts.html similarity index 100% rename from cms/templates/alerts.html rename to cms/templates/ux-alerts.html diff --git a/cms/urls.py b/cms/urls.py index b2c5670913..7a384b3f20 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -82,7 +82,7 @@ urlpatterns = ('', # User creation and updating views urlpatterns += ( - url(r'^alerts$', 'contentstore.views.alerts', name='alerts'), + url(r'^ux-alerts$', 'contentstore.views.ux_alerts', name='ux-alerts'), url(r'^howitworks$', 'contentstore.views.howitworks', name='howitworks'), url(r'^signup$', 'contentstore.views.signup', name='signup'), From 4d136f8d3bc1d247941b68f0af8b2caac4e947b0 Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Thu, 7 Mar 2013 11:51:45 -0500 Subject: [PATCH 025/483] fixed annotation tooltip styling issue in studio --- common/lib/xmodule/xmodule/css/annotatable/display.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index 308b379ec1..c462d4806e 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -127,6 +127,7 @@ $body-font-size: em(14); font-weight: 400; padding: 0 10px 10px 10px; background-color: transparent; + border-color: transparent; } p { color: inherit; From f5c3775b5dcbb8b16e6a0fcd27fd8b835516a56e Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Thu, 7 Mar 2013 12:21:47 -0500 Subject: [PATCH 026/483] fixed the annotation tooltip line height so it is the same in studio and the lms. --- common/lib/xmodule/xmodule/css/annotatable/display.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index c462d4806e..b5739b28fc 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -144,6 +144,7 @@ $body-font-size: em(14); margin: 0px 0px 10px 0; max-height: 225px; overflow: auto; + line-height: normal; } .annotatable-reply { display: block; From 60b060263c15bb90fc658349224c50158609e31d Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Thu, 7 Mar 2013 16:02:22 -0500 Subject: [PATCH 027/483] refactor highlight css to prevent issues with cascade --- common/lib/xmodule/xmodule/css/annotatable/display.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index b5739b28fc..f8ae779b8c 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -55,6 +55,7 @@ $body-font-size: em(14); display: inline; cursor: pointer; + $highlight_index: 0; @each $highlight in ( (yellow rgba(255,255,10,0.3) rgba(255,255,10,0.9)), (red rgba(178,19,16,0.3) rgba(178,19,16,0.9)), @@ -62,12 +63,13 @@ $body-font-size: em(14); (green rgba(25,255,132,0.3) rgba(25,255,132,0.9)), (blue rgba(35,163,255,0.3) rgba(35,163,255,0.9)), (purple rgba(115,9,178,0.3) rgba(115,9,178,0.9))) { - + + $highlight_index: $highlight_index + 1; $marker: nth($highlight,1); $color: nth($highlight,2); $selected_color: nth($highlight,3); - @if $marker == yellow { + @if $highlight_index == 1 { &.highlight { background-color: $color; &.selected { background-color: $selected_color; } @@ -167,5 +169,3 @@ $body-font-size: em(14); border-top-color: rgba(0, 0, 0, .85); } } - - From 49f85211fa5c5550897d25aceb786ac82d1259ee Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Fri, 8 Mar 2013 03:39:34 -0700 Subject: [PATCH 028/483] More documentation for the javascript --- .../js/capa/symbolic_mathjax_preprocessor.js | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/common/static/js/capa/symbolic_mathjax_preprocessor.js b/common/static/js/capa/symbolic_mathjax_preprocessor.js index 19104553dc..766e5efc03 100644 --- a/common/static/js/capa/symbolic_mathjax_preprocessor.js +++ b/common/static/js/capa/symbolic_mathjax_preprocessor.js @@ -1,22 +1,35 @@ +/* This file defines a processor in between the student's math input + (AsciiMath) and what is read by MathJax. It allows for our own + customizations, such as use of the syntax "a_b__x" in superscripts, or + possibly coloring certain variables, etc&. + + It is used in the definition like the following: + + + + +*/ window.SymbolicMathjaxPreprocessor = function () { - this.fn = function (eqn) { - // flags and config - var superscriptsOn = true; + this.fn = function (eqn) { + // flags and config + var superscriptsOn = true; - if (superscriptsOn) { - // find instances of "__" and make them superscripts ("^") and tag them - // as such. Specifcally replace instances of "__X" or "__{XYZ}" with - // "^{CHAR$1}", marking superscripts as different from powers + if (superscriptsOn) { + // find instances of "__" and make them superscripts ("^") and tag them + // as such. Specifcally replace instances of "__X" or "__{XYZ}" with + // "^{CHAR$1}", marking superscripts as different from powers - // a zero width space--this is an invisible character that no one would - // use, that gets passed through MathJax and to the server - var c = "\u200b"; - eqn = eqn.replace(/__(?:([^\{])|\{([^\}]+)\})/g, '^{' + c + '$1$2}'); + // a zero width space--this is an invisible character that no one would + // use, that gets passed through MathJax and to the server + var c = "\u200b"; + eqn = eqn.replace(/__(?:([^\{])|\{([^\}]+)\})/g, '^{' + c + '$1$2}'); - // NOTE: MathJax supports '\class{name}{mathcode}' but not for asciimath - // input, which is too bad. This would be preferable to the char tag - } + // NOTE: MathJax supports '\class{name}{mathcode}' but not for asciimath + // input, which is too bad. This would be preferable to this char tag + } - return eqn; - }; + return eqn; + }; }; From 094458dd6f0e4437a71dcbcd990d31286725dc16 Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Mon, 11 Mar 2013 16:19:36 -0400 Subject: [PATCH 029/483] Modified tooltip positioning on non-overlapping annotation spans. --- .../xmodule/js/src/annotatable/display.coffee | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee index 2ad49ae6d7..523b0e99cf 100644 --- a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee @@ -75,6 +75,7 @@ class @Annotatable classes: 'ui-tooltip-annotatable' events: show: @onShowTip + move: @onMoveTip onClickToggleAnnotations: (e) => @toggleAnnotations() @@ -87,6 +88,40 @@ class @Annotatable onShowTip: (event, api) => event.preventDefault() if @annotationsHidden + onMoveTip: (event, api, position) => + ### + This method handles an edge case in which a tooltip is displayed above + a non-overlapping span like this: + + (( TOOLTIP )) + \/ + text text text ... text text text ...... + + + The problem is that the tooltip looks disconnected from both spans, so + we should re-position the tooltip to appear above the span. + ### + + tip = api.elements.tooltip + adjust_y = api.options.position?.adjust?.y || 0 + target = api.elements.target + rects = $(target).get(0).getClientRects() + is_non_overlapping = (rects?.length == 2 and rects[0].left > rects[1].right) + + if is_non_overlapping + focus_rect = rects[0] + rect_center = focus_rect.left + (focus_rect.width / 2) + rect_top = focus_rect.top + tip_width = $(tip).width() + tip_height = $(tip).height() + tip_left = rect_center - (tip_width / 2) + tip_top = window.pageYOffset + rect_top - tip_height + adjust_y + win_width = $(window).width() + if tip_left + tip_width > win_width + tip_left = win_width - tip_width + position.left = tip_left + position.top = tip_top + getSpanForProblemReturn: (el) -> problem_id = $(@problemReturnSelector).index(el) @$(@spanSelector).filter("[data-problem-id='#{problem_id}']") From fcf82ba2bc44cb701c020d0494e2537139635f27 Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Mon, 11 Mar 2013 18:02:22 -0400 Subject: [PATCH 030/483] fixed pep8 violations for annotation module --- common/lib/xmodule/xmodule/annotatable_module.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/common/lib/xmodule/xmodule/annotatable_module.py b/common/lib/xmodule/xmodule/annotatable_module.py index f093b76f52..1385296ddf 100644 --- a/common/lib/xmodule/xmodule/annotatable_module.py +++ b/common/lib/xmodule/xmodule/annotatable_module.py @@ -11,13 +11,13 @@ from xmodule.contentstore.content import StaticContent log = logging.getLogger(__name__) + class AnnotatableModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'), resource_string(__name__, 'js/src/html/display.coffee'), resource_string(__name__, 'js/src/annotatable/display.coffee')], - 'js': [] - } + 'js': []} js_module_name = "Annotatable" css = {'scss': [resource_string(__name__, 'css/annotatable/display.scss')]} icon_class = 'annotatable' @@ -34,11 +34,11 @@ class AnnotatableModule(XModule): if color is not None: if color in self.highlight_colors: - cls.append('highlight-'+color) + cls.append('highlight-' + color) attr['_delete'] = highlight_key attr['value'] = ' '.join(cls) - return { 'class' : attr } + return {'class': attr} def _get_annotation_data_attr(self, index, el): """ Returns a dict in which the keys are the HTML data attributes @@ -58,7 +58,7 @@ class AnnotatableModule(XModule): if xml_key in el.attrib: value = el.get(xml_key, '') html_key = attrs_map[xml_key] - data_attrs[html_key] = { 'value': value, '_delete': xml_key } + data_attrs[html_key] = {'value': value, '_delete': xml_key} return data_attrs @@ -76,7 +76,6 @@ class AnnotatableModule(XModule): delete_key = attr[key]['_delete'] del el.attrib[delete_key] - def _render_content(self): """ Renders annotatable content with annotation spans and returns HTML. """ xmltree = etree.fromstring(self.content) @@ -123,9 +122,9 @@ class AnnotatableModule(XModule): self.element_id = self.location.html_id() self.highlight_colors = ['yellow', 'orange', 'purple', 'blue', 'green'] + class AnnotatableDescriptor(RawDescriptor): module_class = AnnotatableModule stores_state = True template_dir_name = "annotatable" mako_template = "widgets/raw-edit.html" - From d860b167d6838443d05b5b67805047a7e032f6a3 Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Tue, 12 Mar 2013 14:09:56 -0400 Subject: [PATCH 031/483] fixed tooltip positioning for non-overlapping spans in studio --- .../xmodule/css/annotatable/display.scss | 4 +++ .../xmodule/js/src/annotatable/display.coffee | 34 ++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index f8ae779b8c..6e1a38ee31 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -1,6 +1,10 @@ $border-color: #C8C8C8; $body-font-size: em(14); +.annotatable-wrapper { + position: relative; +} + .annotatable-header { margin-bottom: .5em; .annotatable-title { diff --git a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee index 523b0e99cf..e38e48eeda 100644 --- a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee @@ -1,7 +1,8 @@ class @Annotatable _debug: false - # selectors for the annotatable xmodule + # selectors for the annotatable xmodule + wrapperSelector: '.annotatable-wrapper' toggleAnnotationsSelector: '.annotatable-toggle-annotations' toggleInstructionsSelector: '.annotatable-toggle-instructions' instructionsSelector: '.annotatable-instructions' @@ -61,7 +62,7 @@ class @Annotatable my: 'bottom center' # of tooltip at: 'top center' # of target target: $(el) # where the tooltip was triggered (i.e. the annotation span) - container: @$el + container: @$(@wrapperSelector) adjust: y: -5 show: @@ -104,23 +105,38 @@ class @Annotatable tip = api.elements.tooltip adjust_y = api.options.position?.adjust?.y || 0 + container = api.options.position?.container || $('body') target = api.elements.target + rects = $(target).get(0).getClientRects() is_non_overlapping = (rects?.length == 2 and rects[0].left > rects[1].right) if is_non_overlapping - focus_rect = rects[0] + # we want to choose the largest of the two non-overlapping spans and display + # the tooltip above the center of it (see api.options.position settings) + focus_rect = (if rects[0].width > rects[1].width then rects[0] else rects[1]) rect_center = focus_rect.left + (focus_rect.width / 2) rect_top = focus_rect.top tip_width = $(tip).width() tip_height = $(tip).height() - tip_left = rect_center - (tip_width / 2) - tip_top = window.pageYOffset + rect_top - tip_height + adjust_y + + # tooltip is positioned relative to its container, so we need to factor in offsets + container_offset = $(container).offset() + offset_left = -container_offset.left + offset_top = $('body').scrollTop() - container_offset.top + + tip_left = offset_left + rect_center - (tip_width / 2) + tip_top = offset_top + rect_top - tip_height + adjust_y + + # make sure the new tip position doesn't clip the edges of the screen win_width = $(window).width() - if tip_left + tip_width > win_width - tip_left = win_width - tip_width - position.left = tip_left - position.top = tip_top + if tip_left < offset_left + tip_left = offset_left + else if tip_left + tip_width > win_width + offset_left + tip_left = win_width + offset_left - tip_width + + # final step: update the position object (used by qtip2 to show the tip after the move event) + $.extend position, 'left': tip_left, 'top': tip_top getSpanForProblemReturn: (el) -> problem_id = $(@problemReturnSelector).index(el) From bf6ca1b0e759252795ca89ad905828d30ceada28 Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Tue, 12 Mar 2013 17:32:00 -0400 Subject: [PATCH 032/483] use document to get scrollTop for firefox --- common/lib/xmodule/xmodule/js/src/annotatable/display.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee index e38e48eeda..8a32c8f51e 100644 --- a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee @@ -123,7 +123,7 @@ class @Annotatable # tooltip is positioned relative to its container, so we need to factor in offsets container_offset = $(container).offset() offset_left = -container_offset.left - offset_top = $('body').scrollTop() - container_offset.top + offset_top = $(document).scrollTop() - container_offset.top tip_left = offset_left + rect_center - (tip_width / 2) tip_top = offset_top + rect_top - tip_height + adjust_y From 75b561267c0f8162f9e69b8c085da0544c30bc6b Mon Sep 17 00:00:00 2001 From: Peter Baratta Date: Thu, 14 Mar 2013 05:09:15 -0600 Subject: [PATCH 033/483] Script feature fix --- .../course_data_formats/symbolic_response.rst | 20 ++- lms/lib/symmath/formula.py | 25 ++++ lms/lib/symmath/test_formula.py | 115 ++++++++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 lms/lib/symmath/test_formula.py diff --git a/doc/public/course_data_formats/symbolic_response.rst b/doc/public/course_data_formats/symbolic_response.rst index 773821766e..8463faab3c 100644 --- a/doc/public/course_data_formats/symbolic_response.rst +++ b/doc/public/course_data_formats/symbolic_response.rst @@ -19,8 +19,22 @@ This is a partial list of features, to be revised as we go along: An example of a problem:: - - - + + + It's a bit of a pain to enter that. + + * The script-style math variant. What would be outputted in latex if you + entered ``\mathcal{N}``. This is used in some variables. + + An example:: + + + + + + There is no fancy preprocessing needed, but if you had superscripts or + something, you would need to include that part. diff --git a/lms/lib/symmath/formula.py b/lms/lib/symmath/formula.py index 914a65d1b0..604941ffdd 100644 --- a/lms/lib/symmath/formula.py +++ b/lms/lib/symmath/formula.py @@ -74,6 +74,15 @@ def to_latex(x): # LatexPrinter._print_dot = _print_dot xs = latex(x) xs = xs.replace(r'\XI', 'XI') # workaround for strange greek + + # substitute back into latex form for scripts + # literally something of the form + # 'scriptN' becomes '\\mathcal{N}' + # note: can't use something akin to the _print_hat method above because we sometimes get 'script(N)__B' or more complicated terms + xs = re.sub(r'script([a-zA-Z0-9]+)', + '\\mathcal{\\1}', + xs) + #return '%s{}{}' % (xs[1:-1]) if xs[0] == '$': return '[mathjax]%s[/mathjax]
    ' % (xs[1:-1]) # for sympy v6 @@ -106,6 +115,7 @@ def my_sympify(expr, normphase=False, matrix=False, abcsym=False, do_qubit=False 'i': sympy.I, # lowercase i is also sqrt(-1) 'Q': sympy.Symbol('Q'), # otherwise it is a sympy "ask key" 'I': sympy.Symbol('I'), # otherwise it is sqrt(-1) + 'N': sympy.Symbol('N'), # or it is some kind of sympy function #'X':sympy.sympify('Matrix([[0,1],[1,0]])'), #'Y':sympy.sympify('Matrix([[0,-I],[I,0]])'), #'Z':sympy.sympify('Matrix([[1,0],[0,-1]])'), @@ -266,6 +276,21 @@ class formula(object): elif tag == 'mrow': return ''.join([flatten_pmathml(y) for y in xml]) raise Exception, '[flatten_pmathml] unknown tag %s' % tag + def fix_mathvariant(parent): + '''Fix certain kinds of math variants + + Literally replace N + with 'scriptN'. There have been problems using script_N or script(N) + ''' + for child in parent: + if (gettag(child) == 'mstyle' and child.get('mathvariant') == 'script'): + newchild = etree.Element('mi') + newchild.text = 'script%s' % flatten_pmathml(child[0]) + parent.replace(child, newchild) + fix_mathvariant(child) + fix_mathvariant(xml) + + # find "tagged" superscripts # they have the character \u200b in the superscript # replace them with a__b so snuggle doesn't get confused diff --git a/lms/lib/symmath/test_formula.py b/lms/lib/symmath/test_formula.py new file mode 100644 index 0000000000..d3f16ed6b3 --- /dev/null +++ b/lms/lib/symmath/test_formula.py @@ -0,0 +1,115 @@ +""" +Tests of symbolic math +""" + + +import unittest +import formula +import re +from lxml import etree + +def stripXML(xml): + xml = xml.replace('\n', '') + xml = re.sub(r'\> +\<', '><', xml) + return xml + +class FormulaTest(unittest.TestCase): + # for readability later + mathml_start = '' + mathml_end = '' + + def setUp(self): + self.formulaInstance = formula.formula('') + + def test_replace_mathvariants(self): + expr = ''' + + N +''' + + expected = 'scriptN' + + # wrap + expr = stripXML(self.mathml_start + expr + self.mathml_end) + expected = stripXML(self.mathml_start + expected + self.mathml_end) + + # process the expression + xml = etree.fromstring(expr) + xml = self.formulaInstance.preprocess_pmathml(xml) + test = etree.tostring(xml) + + # success? + self.assertEqual(test, expected) + + + def test_fix_simple_superscripts(self): + expr = ''' + + a + + + b + +''' + + expected = 'a__b' + + # wrap + expr = stripXML(self.mathml_start + expr + self.mathml_end) + expected = stripXML(self.mathml_start + expected + self.mathml_end) + + # process the expression + xml = etree.fromstring(expr) + xml = self.formulaInstance.preprocess_pmathml(xml) + test = etree.tostring(xml) + + # success? + self.assertEqual(test, expected) + + def test_fix_complex_superscripts(self): + expr = ''' + + a + b + + + c + +''' + + expected = 'a_b__c' + + # wrap + expr = stripXML(self.mathml_start + expr + self.mathml_end) + expected = stripXML(self.mathml_start + expected + self.mathml_end) + + # process the expression + xml = etree.fromstring(expr) + xml = self.formulaInstance.preprocess_pmathml(xml) + test = etree.tostring(xml) + + # success? + self.assertEqual(test, expected) + + + def test_fix_msubsup(self): + expr = ''' + + a + b + c +''' + + expected = 'a_bc' # which is (a_b)^c + + # wrap + expr = stripXML(self.mathml_start + expr + self.mathml_end) + expected = stripXML(self.mathml_start + expected + self.mathml_end) + + # process the expression + xml = etree.fromstring(expr) + xml = self.formulaInstance.preprocess_pmathml(xml) + test = etree.tostring(xml) + + # success? + self.assertEqual(test, expected) From 4c8a45f85ecfb6422bd10de3b79f3a5ef51c70f9 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 14 Mar 2013 13:29:26 -0400 Subject: [PATCH 034/483] Code to add in an open ended tab automatically --- cms/djangoapps/contentstore/utils.py | 12 +++++++- cms/djangoapps/contentstore/views.py | 28 +++++++++++++++++-- .../models/settings/course_metadata.py | 9 ++++-- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index cba30131b5..4113361445 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -2,9 +2,10 @@ from django.conf import settings from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError +import copy DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info'] - +OPEN_ENDED_PANEL = {"name" : "Open Ended Panel", "type" : "open_ended"} def get_modulestore(location): """ @@ -158,3 +159,12 @@ def update_item(location, value): get_modulestore(location).delete_item(location) else: get_modulestore(location).update_item(location, value) + +def add_open_ended_panel_tab(course): + course_tabs = copy.copy(course.tabs) + changed = False + if OPEN_ENDED_PANEL not in course_tabs: + course_tabs.append(OPEN_ENDED_PANEL) + changed = True + return changed, course_tabs + diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 6566350f8d..b066f476a3 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -47,6 +47,7 @@ from auth.authz import is_user_in_course_group_role, get_users_in_course_group_b from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, create_all_course_groups from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display, UnitState, get_course_for_item +from .utils import add_open_ended_panel_tab from xmodule.modulestore.xml_importer import import_from_xml from contentstore.course_info_model import get_course_updates,\ @@ -68,7 +69,8 @@ log = logging.getLogger(__name__) COMPONENT_TYPES = ['customtag', 'discussion', 'html', 'problem', 'video'] -ADVANCED_COMPONENT_TYPES = ['annotatable','combinedopenended', 'peergrading'] +OPEN_ENDED_COMPONENT_TYPES = ["combinedopenended", "peergrading"] +ADVANCED_COMPONENT_TYPES = ['annotatable'] + OPEN_ENDED_COMPONENT_TYPES ADVANCED_COMPONENT_CATEGORY = 'advanced' ADVANCED_COMPONENT_POLICY_KEY = 'advanced_modules' @@ -295,6 +297,9 @@ def edit_unit(request, location): # in ADVANCED_COMPONENT_TYPES that should be enabled for the course. course_metadata = CourseMetadata.fetch(course.location) course_advanced_keys = course_metadata.get(ADVANCED_COMPONENT_POLICY_KEY, []) + log.debug(course.tabs) + log.debug(type(course.tabs)) + log.debug("LOOK HERE NOW!!!!!") # Set component types according to course policy file component_types = list(COMPONENT_TYPES) @@ -1329,7 +1334,26 @@ def course_advanced_updates(request, org, course, name): return HttpResponse(json.dumps(CourseMetadata.delete_key(location, json.loads(request.body))), mimetype="application/json") elif real_method == 'POST' or real_method == 'PUT': # NOTE: request.POST is messed up because expect_json cloned_request.POST.copy() is creating a defective entry w/ the whole payload as the key - return HttpResponse(json.dumps(CourseMetadata.update_from_json(location, json.loads(request.body))), mimetype="application/json") + request_body = json.loads(request.body) + filter_tabs = True + if ADVANCED_COMPONENT_POLICY_KEY in request_body: + log.debug("Advanced component in.") + for oe_type in OPEN_ENDED_COMPONENT_TYPES: + log.debug(request_body[ADVANCED_COMPONENT_POLICY_KEY]) + if oe_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: + log.debug("OE type in.") + course_module = modulestore().get_item(location) + changed, new_tabs = add_open_ended_panel_tab(course_module) + log.debug(new_tabs) + if changed: + request_body.update({'tabs' : new_tabs}) + filter_tabs = False + break + log.debug(request_body) + log.debug(filter_tabs) + log.debug("LOOK HERE FOR TAB SAVING!!!!") + response_json = json.dumps(CourseMetadata.update_from_json(location, request_body, filter_tabs=filter_tabs)) + return HttpResponse(response_json, mimetype="application/json") @login_required diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 24245a39d5..af0923213b 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -1,6 +1,7 @@ from xmodule.modulestore import Location from contentstore.utils import get_modulestore from xmodule.x_module import XModuleDescriptor +import copy class CourseMetadata(object): @@ -30,7 +31,7 @@ class CourseMetadata(object): return course @classmethod - def update_from_json(cls, course_location, jsondict): + def update_from_json(cls, course_location, jsondict, filter_tabs=True): """ Decode the json into CourseMetadata and save any changed attrs to the db. @@ -40,9 +41,13 @@ class CourseMetadata(object): dirty = False + filtered_list = copy.copy(cls.FILTERED_LIST) + if not filter_tabs: + filtered_list.remove("tabs") + for k, v in jsondict.iteritems(): # should it be an error if one of the filtered list items is in the payload? - if k not in cls.FILTERED_LIST and (k not in descriptor.metadata or descriptor.metadata[k] != v): + if k not in filtered_list and (k not in descriptor.metadata or descriptor.metadata[k] != v): dirty = True descriptor.metadata[k] = v From a717dffd4886a185ae2d4414f060e295871dbd82 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 14 Mar 2013 13:31:30 -0400 Subject: [PATCH 035/483] Remove debug statements --- cms/djangoapps/contentstore/views.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index b066f476a3..591ec7d7cf 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -297,10 +297,7 @@ def edit_unit(request, location): # in ADVANCED_COMPONENT_TYPES that should be enabled for the course. course_metadata = CourseMetadata.fetch(course.location) course_advanced_keys = course_metadata.get(ADVANCED_COMPONENT_POLICY_KEY, []) - log.debug(course.tabs) - log.debug(type(course.tabs)) - log.debug("LOOK HERE NOW!!!!!") - + # Set component types according to course policy file component_types = list(COMPONENT_TYPES) if isinstance(course_advanced_keys, list): @@ -1337,21 +1334,14 @@ def course_advanced_updates(request, org, course, name): request_body = json.loads(request.body) filter_tabs = True if ADVANCED_COMPONENT_POLICY_KEY in request_body: - log.debug("Advanced component in.") for oe_type in OPEN_ENDED_COMPONENT_TYPES: - log.debug(request_body[ADVANCED_COMPONENT_POLICY_KEY]) if oe_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: - log.debug("OE type in.") course_module = modulestore().get_item(location) changed, new_tabs = add_open_ended_panel_tab(course_module) - log.debug(new_tabs) if changed: request_body.update({'tabs' : new_tabs}) filter_tabs = False break - log.debug(request_body) - log.debug(filter_tabs) - log.debug("LOOK HERE FOR TAB SAVING!!!!") response_json = json.dumps(CourseMetadata.update_from_json(location, request_body, filter_tabs=filter_tabs)) return HttpResponse(response_json, mimetype="application/json") From 10eb7e45ea58a776113087515c1a00748f954320 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 14 Mar 2013 13:42:41 -0400 Subject: [PATCH 036/483] Add in some docs --- cms/djangoapps/contentstore/utils.py | 8 ++++++++ cms/djangoapps/contentstore/views.py | 14 +++++++++++--- cms/djangoapps/models/settings/course_metadata.py | 2 ++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 4113361445..7e034d8da8 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -161,9 +161,17 @@ def update_item(location, value): get_modulestore(location).update_item(location, value) def add_open_ended_panel_tab(course): + """ + Used to add the open ended panel tab to a course if it does not exist. + @param course: A course object from the modulestore. + @return: Boolean indicating whether or not a tab was added and a list of tabs for the course. + """ + #Copy course tabs course_tabs = copy.copy(course.tabs) changed = False + #Check to see if open ended panel is defined in the course if OPEN_ENDED_PANEL not in course_tabs: + #Add panel to the tabs if it is not defined course_tabs.append(OPEN_ENDED_PANEL) changed = True return changed, course_tabs diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 591ec7d7cf..b7fcc9988e 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -297,7 +297,7 @@ def edit_unit(request, location): # in ADVANCED_COMPONENT_TYPES that should be enabled for the course. course_metadata = CourseMetadata.fetch(course.location) course_advanced_keys = course_metadata.get(ADVANCED_COMPONENT_POLICY_KEY, []) - + # Set component types according to course policy file component_types = list(COMPONENT_TYPES) if isinstance(course_advanced_keys, list): @@ -310,7 +310,6 @@ def edit_unit(request, location): templates = modulestore().get_items(Location('i4x', 'edx', 'templates')) for template in templates: category = template.location.category - if category in course_advanced_keys: category = ADVANCED_COMPONENT_CATEGORY @@ -1332,15 +1331,24 @@ def course_advanced_updates(request, org, course, name): elif real_method == 'POST' or real_method == 'PUT': # NOTE: request.POST is messed up because expect_json cloned_request.POST.copy() is creating a defective entry w/ the whole payload as the key request_body = json.loads(request.body) + #Whether or not to filter the tabs key out of the settings metadata filter_tabs = True + #Check to see if the user instantiated any advanced components. This is a hack to add the open ended panel tab + #to a course automatically if the user has indicated that they want to edit the combinedopenended or peergrading + #module. if ADVANCED_COMPONENT_POLICY_KEY in request_body: + #Check to see if the user instantiated any open ended components for oe_type in OPEN_ENDED_COMPONENT_TYPES: if oe_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: + #Get the course so that we can scrape current tabs course_module = modulestore().get_item(location) + #Add an open ended tab to the course if needed changed, new_tabs = add_open_ended_panel_tab(course_module) + #If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json if changed: request_body.update({'tabs' : new_tabs}) - filter_tabs = False + #Indicate that tabs should not be filtered out of the metadata + filter_tabs = False break response_json = json.dumps(CourseMetadata.update_from_json(location, request_body, filter_tabs=filter_tabs)) return HttpResponse(response_json, mimetype="application/json") diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index af0923213b..2747cc0751 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -41,7 +41,9 @@ class CourseMetadata(object): dirty = False + #Copy the filtered list to avoid permanently changing the class attribute filtered_list = copy.copy(cls.FILTERED_LIST) + #Don't filter on the tab attribute if filter_tabs is False if not filter_tabs: filtered_list.remove("tabs") From 03caf94c9842ddc89224cedd917d72cc83f76fbd Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 15 Mar 2013 16:26:39 -0400 Subject: [PATCH 037/483] remove redundent XML attribtues which are in metadata --- common/lib/xmodule/xmodule/templates/discussion/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/templates/discussion/default.yaml b/common/lib/xmodule/xmodule/templates/discussion/default.yaml index d34e6378e6..49c0ce9c48 100644 --- a/common/lib/xmodule/xmodule/templates/discussion/default.yaml +++ b/common/lib/xmodule/xmodule/templates/discussion/default.yaml @@ -5,5 +5,5 @@ metadata: id: 6002x_group_discussion_by_this discussion_category: Week 1 data: | - + children: [] From b54ebb346027dbe46e17d606a9865f95cbf062a5 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 15 Mar 2013 22:43:20 -0400 Subject: [PATCH 038/483] make discussion module use MetadataOnlyEditingDescriptor which will not present a code edit region and only display the metadata editor --- cms/templates/widgets/metadata-only-edit.html | 1 + common/lib/xmodule/xmodule/discussion_module.py | 3 ++- common/lib/xmodule/xmodule/editing_module.py | 10 ++++++++++ .../xmodule/js/src/raw/edit/metadata-only.coffee | 5 +++++ common/lib/xmodule/xmodule/raw_module.py | 2 +- 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 cms/templates/widgets/metadata-only-edit.html create mode 100644 common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.coffee diff --git a/cms/templates/widgets/metadata-only-edit.html b/cms/templates/widgets/metadata-only-edit.html new file mode 100644 index 0000000000..a784f3798c --- /dev/null +++ b/cms/templates/widgets/metadata-only-edit.html @@ -0,0 +1 @@ +<%include file="metadata-edit.html" /> diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index 7725a88e77..a0a5207e16 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -3,6 +3,7 @@ from pkg_resources import resource_string, resource_listdir from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor +from xmodule.editing_module import MetadataOnlyEditingDescriptor from xblock.core import String, Scope @@ -28,7 +29,7 @@ class DiscussionModule(DiscussionFields, XModule): return self.system.render_template('discussion/_discussion_module.html', context) -class DiscussionDescriptor(DiscussionFields, RawDescriptor): +class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawDescriptor): module_class = DiscussionModule template_dir_name = "discussion" diff --git a/common/lib/xmodule/xmodule/editing_module.py b/common/lib/xmodule/xmodule/editing_module.py index b93727a96b..1f07861ae8 100644 --- a/common/lib/xmodule/xmodule/editing_module.py +++ b/common/lib/xmodule/xmodule/editing_module.py @@ -40,6 +40,16 @@ class XMLEditingDescriptor(EditingDescriptor): js = {'coffee': [resource_string(__name__, 'js/src/raw/edit/xml.coffee')]} js_module_name = "XMLEditingDescriptor" +class MetadataOnlyEditingDescriptor(EditingDescriptor): + """ + Module that provides a raw editing view of its data as XML. It does not perform + any validation of its definition + """ + + js = {'coffee': [resource_string(__name__, 'js/src/raw/edit/metadata-only.coffee')]} + js_module_name = "MetadataOnlyEditingDescriptor" + + mako_template = "widgets/metadata-only-edit.html" class JSONEditingDescriptor(EditingDescriptor): """ diff --git a/common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.coffee b/common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.coffee new file mode 100644 index 0000000000..8c9afe86aa --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.coffee @@ -0,0 +1,5 @@ +class @MetadataOnlyEditingDescriptor extends XModule.Descriptor + constructor: (@element) -> + + save: -> + data: null diff --git a/common/lib/xmodule/xmodule/raw_module.py b/common/lib/xmodule/xmodule/raw_module.py index 2c6e157018..6b5c2441be 100644 --- a/common/lib/xmodule/xmodule/raw_module.py +++ b/common/lib/xmodule/xmodule/raw_module.py @@ -1,5 +1,5 @@ from lxml import etree -from xmodule.editing_module import XMLEditingDescriptor +from xmodule.editing_module import XMLEditingDescriptor, MetadataOnlyEditingDescriptor from xmodule.xml_module import XmlDescriptor import logging import sys From 3e65a688288695c8b1042de9ce56f3cc175d47c4 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 15 Mar 2013 23:01:03 -0400 Subject: [PATCH 039/483] auto generate the discussion id on create. Also add 'discussion_id' to the list of 'system-metadata' so that end users cannot edit it --- common/lib/xmodule/xmodule/modulestore/mongo.py | 7 +++++++ .../lib/xmodule/xmodule/templates/discussion/default.yaml | 2 +- common/lib/xmodule/xmodule/x_module.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index c5e5bbfdf8..d115a92852 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -9,6 +9,7 @@ from fs.osfs import OSFS from itertools import repeat from path import path from datetime import datetime, timedelta +from uuid import uuid4 from importlib import import_module from xmodule.errortracker import null_error_tracker, exc_info_to_str @@ -496,6 +497,12 @@ class MongoModuleStore(ModuleStoreBase): """ try: source_item = self.collection.find_one(location_to_query(source)) + + # allow for some programmatically generated substitutions in metadata, e.g. Discussion_id's should be auto-generated + for key in source_item['metadata'].keys(): + if source_item['metadata'][key] == '$$GUID$$': + source_item['metadata'][key] = uuid4().hex + source_item['_id'] = Location(location).dict() self.collection.insert(source_item) item = self._load_items([source_item])[0] diff --git a/common/lib/xmodule/xmodule/templates/discussion/default.yaml b/common/lib/xmodule/xmodule/templates/discussion/default.yaml index 49c0ce9c48..049e34b3e7 100644 --- a/common/lib/xmodule/xmodule/templates/discussion/default.yaml +++ b/common/lib/xmodule/xmodule/templates/discussion/default.yaml @@ -2,7 +2,7 @@ metadata: display_name: Discussion Tag for: Topic-Level Student-Visible Label - id: 6002x_group_discussion_by_this + id: $$GUID$$ discussion_category: Week 1 data: | diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 02835e0d5d..5e1ba826b8 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -340,7 +340,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): # cdodge: this is a list of metadata names which are 'system' metadata # and should not be edited by an end-user - system_metadata_fields = ['data_dir', 'published_date', 'published_by', 'is_draft'] + system_metadata_fields = ['data_dir', 'published_date', 'published_by', 'is_draft', 'discussion_id'] # A list of descriptor attributes that must be equal for the descriptors to # be equal From 9a18685c1c674668500d2382e223d932f6c5177d Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 18 Mar 2013 09:01:54 -0400 Subject: [PATCH 040/483] add a discussion module write through cache to speed up Forum rendering --- cms/djangoapps/contentstore/utils.py | 1 + cms/one_time_startup.py | 8 +++ .../cache_toolbox/discussion_cache.py | 52 +++++++++++++++++++ .../xmodule/xmodule/modulestore/__init__.py | 3 +- .../lib/xmodule/xmodule/modulestore/mongo.py | 20 ++++++- lms/djangoapps/django_comment_client/utils.py | 16 +----- 6 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 common/djangoapps/cache_toolbox/discussion_cache.py diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 0a99441fe9..7d9d539604 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -6,6 +6,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info'] + def get_modulestore(location): """ Returns the correct modulestore to use for modifying the specified location diff --git a/cms/one_time_startup.py b/cms/one_time_startup.py index 38a2fef847..961ffd15fb 100644 --- a/cms/one_time_startup.py +++ b/cms/one_time_startup.py @@ -1,6 +1,8 @@ from dogapi import dog_http_api, dog_stats_api from django.conf import settings from xmodule.modulestore.django import modulestore +from cache_toolbox.discussion_cache import discussion_cache_register_for_updates +from django.dispatch import Signal from django.core.cache import get_cache, InvalidCacheBackendError @@ -9,6 +11,12 @@ for store_name in settings.MODULESTORE: store = modulestore(store_name) store.metadata_inheritance_cache = cache + modulestore_update_signal = Signal( + providing_args=['modulestore', 'course_id', 'location'] + ) + store.modulestore_update_signal = modulestore_update_signal + discussion_cache_register_for_updates(store) + if hasattr(settings, 'DATADOG_API'): dog_http_api.api_key = settings.DATADOG_API dog_stats_api.start(api_key=settings.DATADOG_API, statsd=True) diff --git a/common/djangoapps/cache_toolbox/discussion_cache.py b/common/djangoapps/cache_toolbox/discussion_cache.py new file mode 100644 index 0000000000..57390528bf --- /dev/null +++ b/common/djangoapps/cache_toolbox/discussion_cache.py @@ -0,0 +1,52 @@ +import logging +from django.core.cache import cache, get_cache +from datetime import datetime, timedelta + +def _get_discussion_cache(): + return cache + + +def get_discussion_cache_key(course_id): + return 'discussion_{0}'.format(course_id) + + +def get_discussion_cache_entry(modulestore, course_id): + cache_entry = None + cache = _get_discussion_cache() + + if cache is not None: + cache_entry = cache.get(get_discussion_cache_key(course_id), None) + if cache_entry is not None: + delta = datetime.now() - cache_entry.get('timestamp', datetime.min) + if delta > Timedelta(0,300): + cache_entry = None + + if cache_entry is None: + cache_entry = generate_discussion_cache_entry(modulestore, course_id) + + return cache_entry.get('modules',[]) + + +def generate_discussion_cache_entry(modulestore, course_id): + components = course_id.split('/') + all_discussion_modules = modulestore.get_items(['i4x', components[0], components[1], 'discussion', None], + course_id=course_id) + + cache = _get_discussion_cache() + if cache is not None: + cache.set(get_discussion_cache_key(course_id), {'modules': all_discussion_modules, 'timestamp': datetime.now()}) + return all_discussion_modules + + +def modulestore_update_signal_handler(modulestore = None, course_id = None, location = None, **kwargs): + """called when there is an write event in our modulestore + """ + if location.category == 'discussion': + logging.debug('******* got modulestore update signal. Regenerating discussion cache for {0}'.format(course_id)) + # refresh the cache entry if we've changed a discussion module + generate_discussion_cache_entry(modulestore, course_id) + + +def discussion_cache_register_for_updates(modulestore): + if modulestore.modulestore_update_signal is not None: + modulestore.modulestore_update_signal.connect(modulestore_update_signal_handler) \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index 022e016a58..a1a159b700 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -251,7 +251,6 @@ class Location(_LocationBase): def __repr__(self): return "Location%s" % repr(tuple(self)) - @property def course_id(self): """Return the ID of the Course that this item belongs to by looking @@ -413,7 +412,6 @@ class ModuleStore(object): return courses - class ModuleStoreBase(ModuleStore): ''' Implement interface functionality that can be shared. @@ -424,6 +422,7 @@ class ModuleStoreBase(ModuleStore): ''' self._location_errors = {} # location -> ErrorLog self.metadata_inheritance_cache = None + self.modulestore_update_signal = None # can be set by runtime to route notifications of datastore changes def _get_errorlog(self, location): """ diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index d115a92852..b0b8b11aef 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -31,6 +31,10 @@ log = logging.getLogger(__name__) # there is only one revision for each item. Once we start versioning inside the CMS, # that assumption will have to change +def get_course_id_no_run(location): + ''' + ''' + return "/".join([location.org, location.course]) class MongoKeyValueStore(KeyValueStore): """ @@ -527,6 +531,15 @@ class MongoModuleStore(ModuleStoreBase): # recompute (and update) the metadata inheritance tree which is cached self.get_cached_metadata_inheritance_tree(Location(location), force_refresh = True) + self.fire_updated_modulestore_signal(get_course_id_no_run(Location(location)), Location(location)) + + def fire_updated_modulestore_signal(self, course_id, location): + if self.modulestore_update_signal is not None: + self.modulestore_update_signal.send(None, + modulestore = self, + course_id = course_id, + location = location + ) def get_course_for_item(self, location, depth=0): ''' @@ -594,6 +607,8 @@ class MongoModuleStore(ModuleStoreBase): self._update_single_item(location, {'definition.children': children}) # recompute (and update) the metadata inheritance tree which is cached self.get_cached_metadata_inheritance_tree(Location(location), force_refresh = True) + # fire signal that we've written to DB + self.fire_updated_modulestore_signal(get_course_id_no_run(Location(location)), Location(location)) def update_metadata(self, location, metadata): """ @@ -619,7 +634,8 @@ class MongoModuleStore(ModuleStoreBase): self._update_single_item(location, {'metadata': metadata}) # recompute (and update) the metadata inheritance tree which is cached - self.get_cached_metadata_inheritance_tree(loc, force_refresh = True) + self.get_cached_metadata_inheritance_tree(loc, force_refresh = True) + self.fire_updated_modulestore_signal(get_course_id_no_run(Location(location)), Location(location)) def delete_item(self, location): """ @@ -640,7 +656,7 @@ class MongoModuleStore(ModuleStoreBase): self.collection.remove({'_id': Location(location).dict()}) # recompute (and update) the metadata inheritance tree which is cached self.get_cached_metadata_inheritance_tree(Location(location), force_refresh = True) - + self.fire_updated_modulestore_signal(get_course_id_no_run(Location(location)), Location(location)) def get_parent_locations(self, location, course_id): '''Find all locations that are the parents of this location in this diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 42233b84da..fbd0b29eca 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -16,6 +16,7 @@ from django.utils import simplejson from django_comment_client.models import Role from django_comment_client.permissions import check_permissions_by_view from xmodule.modulestore.exceptions import NoPathToItem +from cache_toolbox.discussion_cache import get_discussion_cache_entry from mitxmako import middleware import pystache_custom as pystache @@ -146,28 +147,15 @@ def sort_map_entries(category_map): def initialize_discussion_info(course): - global _DISCUSSIONINFO - # only cache in-memory discussion information for 10 minutes - # this is because we need a short-term hack fix for - # mongo-backed courseware whereby new discussion modules can be added - # without LMS service restart - - if _DISCUSSIONINFO[course.id]: - timestamp = _DISCUSSIONINFO[course.id].get('timestamp', datetime.now()) - age = datetime.now() - timestamp - # expire every 5 minutes - if age.seconds < 300: - return - course_id = course.id discussion_id_map = {} unexpanded_category_map = defaultdict(list) # get all discussion models within this course_id - all_modules = modulestore().get_items(['i4x', course.location.org, course.location.course, 'discussion', None], course_id=course_id) + all_modules = get_discussion_cache_entry(modulestore(), course_id) for module in all_modules: skip_module = False From 16f9f6ef4f0293aab1e5ff41e8d4d25eb3b24f92 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 18 Mar 2013 13:15:01 -0400 Subject: [PATCH 041/483] studio - tender widget: added initial reference and behavior --- cms/static/sass/_tender-widget.scss | 20 ++++++++++++++++++++ cms/static/sass/base-style.scss | 1 + cms/templates/base.html | 1 + cms/templates/widgets/footer.html | 7 +++---- cms/templates/widgets/tender.html | 13 +++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 cms/static/sass/_tender-widget.scss create mode 100644 cms/templates/widgets/tender.html diff --git a/cms/static/sass/_tender-widget.scss b/cms/static/sass/_tender-widget.scss new file mode 100644 index 0000000000..fa406009ee --- /dev/null +++ b/cms/static/sass/_tender-widget.scss @@ -0,0 +1,20 @@ +// tender help/support widget +// ==================== + +// tender help link + + +// ==================== + +// tender modal UI +#tender_window { + +} + +#tender_frame { + +} + +.widget-layout { + font-family: 'Open Sans', sans-serif; +} \ No newline at end of file diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index dceac4233d..bdfdab2cb3 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -32,6 +32,7 @@ @import "account"; @import "index"; @import 'jquery-ui-calendar'; +@import 'tender-widget'; @import 'content-types'; diff --git a/cms/templates/base.html b/cms/templates/base.html index f7b2c46f61..5e6f4348b0 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -56,6 +56,7 @@ <%include file="widgets/footer.html" /> <%block name="jsextra"> + <%include file="widgets/tender.html" /> diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index 0f265dfc2c..c38d5dec7f 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -17,13 +17,12 @@ + - - % if user.is_authenticated(): - - % endif diff --git a/cms/templates/widgets/tender.html b/cms/templates/widgets/tender.html new file mode 100644 index 0000000000..e2bba3d2ef --- /dev/null +++ b/cms/templates/widgets/tender.html @@ -0,0 +1,13 @@ +% if user.is_authenticated(): + + +% endif \ No newline at end of file From 7507f91d2274a64c2993717bcbecb078346461a3 Mon Sep 17 00:00:00 2001 From: Arthur Barrett Date: Mon, 18 Mar 2013 15:43:43 -0400 Subject: [PATCH 042/483] Added TODO and explanation for the top-level scss variables. --- common/lib/xmodule/xmodule/css/annotatable/display.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index 6e1a38ee31..e2c095de2d 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -1,3 +1,9 @@ +/* TODO: move top-level variables to a common _variables.scss. + * NOTE: These variables were only added here because when this was integrated with the CMS, + * SASS compilation errors were triggered because the CMS didn't have the same variables defined + * that the LMS did, so the quick fix was to localize the LMS variables not shared by the CMS. + * -Abarrett and Vshnayder + */ $border-color: #C8C8C8; $body-font-size: em(14); From c901cecdea15249a63e41e4468fe5467b4b83999 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 18 Mar 2013 16:23:30 -0400 Subject: [PATCH 043/483] studio - tender widget: added compromise of style given iframe and clunky DOM elements that tender is using. meh. --- cms/static/sass/_tender-widget.scss | 87 ++++++++++++++++++++++++++--- cms/static/sass/_variables.scss | 1 + cms/templates/base.html | 2 +- cms/templates/widgets/footer.html | 2 +- 4 files changed, 82 insertions(+), 10 deletions(-) diff --git a/cms/static/sass/_tender-widget.scss b/cms/static/sass/_tender-widget.scss index fa406009ee..422be36908 100644 --- a/cms/static/sass/_tender-widget.scss +++ b/cms/static/sass/_tender-widget.scss @@ -1,20 +1,91 @@ // tender help/support widget // ==================== -// tender help link - - -// ==================== - -// tender modal UI -#tender_window { - +#tender_frame, #tender_window { + background-image: none !important; } #tender_frame { + @include border-radius(3px); + @include box-shadow(0 2px 3px $shadow); + border: 1px solid $gray; + background: $white; +} +#tender_closer { + color: $blue-l2 !important; + margin-top: 15px; + margin-right: 5px; +} + +// ==================== + +// tender style overrides - not rendered through here, but an archive is needed +#tender_frame iframe html { + font-size: 62.5%; } .widget-layout { font-family: 'Open Sans', sans-serif; +} + +.widget-layout .search, +.widget-layout .tabs, +.widget-layout .header h1 a { + display: none; +} + +.widget-layout .header { + background: rgb(85, 151, 221); + padding: 20px; +} + +.widget-layout h1, .widget-layout h2, .widget-layout h3, .widget-layout h4, .widget-layout h5, .widget-layout h6, .widget-layout label { + font-weight: 600; +} + +.widget-layout .header h1 { + font-weight: 700; + font-size: 24px; + font-size: 2.4rem; +} + +.widget-layout .content { + padding: 20px; +} + +.widget-layout label { + font-size: 14px; + font-size: 1.4rem; + margin-bottom: 5px; + color: rgb(127,127,127) !important; +} + +.widget-layout input[type="text"], .widget-layout textarea { + padding: 10px; + font-size: 16px; + font-size: 1.6rem; + color: rgb(0,0,0) !important; +} + +.widget-layout textarea { + width: 97%; +} + +.widget-layout .form-actions { + border-top: 1px solid #ccc; + margin-top: 10px; + padding-top: 10px; +} + +.widget-layout dl.form { + margin-bottom: 15px; +} + +.widget-layout #brain_buster_captcha { + display: block; + width: 100%; + border-bottom: 1px solid #ccc; + margin-bottom: 10px; + padding-bottom: 10px; } \ No newline at end of file diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index 4d8e26b2f9..8e588b6234 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -16,6 +16,7 @@ $error-red: rgb(253, 87, 87); // colors - new for re-org $black: rgb(0,0,0); +$black-t0: rgba(0,0,0,0.125); $white: rgb(255,255,255); $gray: rgb(127,127,127); diff --git a/cms/templates/base.html b/cms/templates/base.html index 5e6f4348b0..77323be4d1 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -54,9 +54,9 @@ <%block name="content"> <%include file="widgets/footer.html" /> + <%include file="widgets/tender.html" /> <%block name="jsextra"> - <%include file="widgets/tender.html" /> diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index c38d5dec7f..e3063dafa7 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -18,7 +18,7 @@ edX Studio Help - diff --git a/cms/templates/widgets/tender.html b/cms/templates/widgets/tender.html index e2bba3d2ef..300b71701c 100644 --- a/cms/templates/widgets/tender.html +++ b/cms/templates/widgets/tender.html @@ -1,12 +1,12 @@ % if user.is_authenticated(): +Provide Feedback From 69c95ca785b60da37ea5e3aadadf177bef1f4c01 Mon Sep 17 00:00:00 2001 From: cahrens Date: Tue, 26 Mar 2013 09:51:24 -0400 Subject: [PATCH 061/483] Newline cleanup. --- cms/static/js/base.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 0521371b6a..f623607d14 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -303,6 +303,7 @@ function saveSubsection() { data: JSON.stringify({ 'id': id, 'metadata': metadata}), success: function () { $spinner.delay(500).fadeOut(150); + $changedInput = null; }, error: function () { showToastMessage('There has been an error while saving your changes.'); From df935d422d31fcf34489f8b0fa501a4ac627212a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 26 Mar 2013 09:52:26 -0400 Subject: [PATCH 062/483] Fix issues with open ended image grading and peer grading centralized module finder. --- .../open_ended_grading_classes/openendedchild.py | 4 ---- lms/djangoapps/courseware/module_render.py | 10 +++------- lms/djangoapps/open_ended_grading/views.py | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py index 2e49565bec..b9341f0cbe 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py @@ -357,10 +357,6 @@ class OpenEndedChild(object): if get_data['can_upload_files'] in ['true', '1']: has_file_to_upload = True file = get_data['student_file'][0] - if self.system.track_fuction: - self.system.track_function('open_ended_image_upload', {'filename': file.name}) - else: - log.info("No tracking function found when uploading image.") uploaded_to_s3, image_ok, s3_public_url = self.upload_image_to_s3(file) if uploaded_to_s3: image_tag = self.generate_image_tag_from_url(s3_public_url, file.name) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 973940d784..a1c09d3d83 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -208,9 +208,6 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours 'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS } - def get_or_default(key, default): - getattr(settings, key, default) - #This is a hacky way to pass settings to the combined open ended xmodule #It needs an S3 interface to upload images to S3 #It needs the open ended grading interface in order to get peer grading to be done @@ -226,12 +223,11 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING if is_descriptor_combined_open_ended: s3_interface = { - 'access_key' : get_or_default('AWS_ACCESS_KEY_ID',''), - 'secret_access_key' : get_or_default('AWS_SECRET_ACCESS_KEY',''), - 'storage_bucket_name' : get_or_default('AWS_STORAGE_BUCKET_NAME','') + 'access_key' : getattr(settings,'AWS_ACCESS_KEY_ID',''), + 'secret_access_key' : getattr(settings,'AWS_SECRET_ACCESS_KEY',''), + 'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','') } - def inner_get_module(descriptor): """ Delegate to get_module. It does an access check, so may return None diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 65cfe22ed0..78da00bf2b 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -111,7 +111,7 @@ def peer_grading(request, course_id): #Get the peer grading modules currently in the course items = modulestore().get_items(['i4x', None, course_id_parts[1], 'peergrading', None]) #See if any of the modules are centralized modules (ie display info from multiple problems) - items = [i for i in items if i.metadata.get("use_for_single_location", True) in false_dict] + items = [i for i in items if getattr(i,"use_for_single_location", True) in false_dict] #Get the first one item_location = items[0].location #Generate a url for the first module and redirect the user to it From d4615da555f77a15ba7c4f70d380f813f195a6f7 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 26 Mar 2013 09:57:52 -0400 Subject: [PATCH 063/483] Adjust max image dim, add in safety for rewriting links --- .../combined_open_ended_modulev1.py | 6 +++++- .../open_ended_image_submission.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 98a54601de..c7df87fd45 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -363,7 +363,11 @@ class CombinedOpenEndedV1Module(): """ self.update_task_states() html = self.current_task.get_html(self.system) - return_html = rewrite_links(html, self.rewrite_content_links) + return_html = html + try: + return_html = rewrite_links(html, self.rewrite_content_links) + except: + pass return return_html def get_current_attributes(self, task_number): diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py index 6956f336a5..759645840f 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py @@ -36,7 +36,7 @@ ALLOWABLE_IMAGE_SUFFIXES = [ ] #Maximum allowed dimensions (x and y) for an uploaded image -MAX_ALLOWED_IMAGE_DIM = 1500 +MAX_ALLOWED_IMAGE_DIM = 2000 #Dimensions to which image is resized before it is evaluated for color count, etc MAX_IMAGE_DIM = 150 From 8afe2eb001a925bd49e9e5fb9678c3572e47ad0e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 26 Mar 2013 10:35:47 -0400 Subject: [PATCH 064/483] Increase max score allowed --- .../open_ended_grading_classes/combined_open_ended_modulev1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index c7df87fd45..f88fd9ab82 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -24,7 +24,7 @@ MAX_ATTEMPTS = 1 MAX_SCORE = 1 #The highest score allowed for the overall xmodule and for each rubric point -MAX_SCORE_ALLOWED = 3 +MAX_SCORE_ALLOWED = 50 #If true, default behavior is to score module as a practice problem. Otherwise, no grade at all is shown in progress #Metadata overrides this. From f681d4300d7c9226eed57ee117169126598f9d42 Mon Sep 17 00:00:00 2001 From: cahrens Date: Tue, 26 Mar 2013 10:42:44 -0400 Subject: [PATCH 065/483] More cleanup in base.js. --- cms/static/js/base.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index f623607d14..bd8dc0bae8 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -236,7 +236,7 @@ function getEdxTimeFromDateTimeVals(date_val, time_val, format) { time_val = '00:00'; // Note, we are using date.js utility which has better parsing abilities than the built in JS date parsing - date = Date.parse(date_val + " " + time_val); + var date = Date.parse(date_val + " " + time_val); if (format == null) format = 'yyyy-MM-ddTHH:mm'; @@ -254,6 +254,7 @@ function getEdxTimeFromDateTimeInputs(date_id, time_id, format) { } function autosaveInput(e) { + var self = this; if (this.saveTimer) { clearTimeout(this.saveTimer); } @@ -261,7 +262,7 @@ function autosaveInput(e) { this.saveTimer = setTimeout(function () { $changedInput = $(e.target); saveSubsection(); - this.saveTimer = null; + self.saveTimer = null; }, 500); } From 97cb4910a7b8d36123941538776a1d53ec4be034 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 26 Mar 2013 11:04:14 -0400 Subject: [PATCH 066/483] Add in default bucket, edit image url checks --- .../open_ended_grading_classes/open_ended_image_submission.py | 2 +- lms/djangoapps/courseware/module_render.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py index 759645840f..2eb9502269 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_image_submission.py @@ -178,7 +178,7 @@ class URLProperties(object): Runs all available url tests @return: True if URL passes tests, false if not. """ - url_is_okay = self.check_suffix() and self.check_if_parses() and self.check_domain() + url_is_okay = self.check_suffix() and self.check_if_parses() return url_is_okay def check_domain(self): diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index a1c09d3d83..15f95f1beb 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -225,7 +225,7 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours s3_interface = { 'access_key' : getattr(settings,'AWS_ACCESS_KEY_ID',''), 'secret_access_key' : getattr(settings,'AWS_SECRET_ACCESS_KEY',''), - 'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','') + 'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','openended') } def inner_get_module(descriptor): From 87f545329a6f9f75fed6cdc16502a23e124a9ebe Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 26 Mar 2013 11:05:33 -0400 Subject: [PATCH 067/483] studio - adding in tender-widget sass file --- cms/static/sass/elements/_tender-widget.scss | 142 +++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 cms/static/sass/elements/_tender-widget.scss diff --git a/cms/static/sass/elements/_tender-widget.scss b/cms/static/sass/elements/_tender-widget.scss new file mode 100644 index 0000000000..fce62b8675 --- /dev/null +++ b/cms/static/sass/elements/_tender-widget.scss @@ -0,0 +1,142 @@ +// tender help/support widget +// ==================== + +#tender_frame, #tender_window { + background-image: none !important; + background: none; +} + +#tender_window { + @include border-radius(3px); + @include box-shadow(0 2px 3px $shadow); + background: $white !important; + border: 1px solid $gray; +} + +#tender_window { + padding: 0 !important; +} + +#tender_frame { + background: $white; +} + +#tender_closer { + color: $blue-l2 !important; + margin-top: 15px; + margin-right: 5px; + text-transform: uppercase; + + &:hover { + color: $blue-l4 !important; + } +} + +// ==================== + +// tender style overrides - not rendered through here, but an archive is needed +#tender_frame iframe html { + font-size: 62.5%; +} + +.widget-layout { + font-family: 'Open Sans', sans-serif; +} + +.widget-layout .search, +.widget-layout .tabs, +.widget-layout .footer, +.widget-layout .header h1 a { + display: none; +} + +.widget-layout .header { + background: rgb(85, 151, 221); + padding: 20px; +} + +.widget-layout h1, .widget-layout h2, .widget-layout h3, .widget-layout h4, .widget-layout h5, .widget-layout h6, .widget-layout label { + font-weight: 600; +} + +.widget-layout .header h1 { + font-weight: 500; + font-size: 24px; +} + +.widget-layout .content { + overflow: auto; + padding: 20px; +} + +.widget-layout label { + font-size: 14px; + margin-bottom: 5px; + color: #4c4c4c; + font-weight: 500; +} + +.widget-layout input[type="text"], .widget-layout textarea { + padding: 10px; + font-size: 16px; + color: rgb(0,0,0) !important; + border: 1px solid #b0b6c2; + border-radius: 2px; + background-color: #edf1f5; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #edf1f5),color-stop(100%, #fdfdfe)); + background-image: -webkit-linear-gradient(top, #edf1f5,#fdfdfe); + background-image: -moz-linear-gradient(top, #edf1f5,#fdfdfe); + background-image: -ms-linear-gradient(top, #edf1f5,#fdfdfe); + background-image: -o-linear-gradient(top, #edf1f5,#fdfdfe); + background-image: linear-gradient(top, #edf1f5,#fdfdfe); + background-color: #edf1f5; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.1) inset; + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.1) inset; + box-shadow: 0 1px 2px rgba(0,0,0,0.1) inset; +} + +.widget-layout input[type="text"]:focus, .widget-layout textarea:focus { + background-color: #fffcf1; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fffcf1),color-stop(100%, #fffefd)); + background-image: -webkit-linear-gradient(top, #fffcf1,#fffefd); + background-image: -moz-linear-gradient(top, #fffcf1,#fffefd); + background-image: -ms-linear-gradient(top, #fffcf1,#fffefd); + background-image: -o-linear-gradient(top, #fffcf1,#fffefd); + background-image: linear-gradient(top, #fffcf1,#fffefd); + outline: 0; +} + +.widget-layout textarea { + width: 97%; +} + +.widget-layout .form-actions { + border-top: 1px solid #ccc; + margin-top: 10px; + padding-top: 10px; +} + +.widget-layout dl.form { + float: none; + width: 100%; + border-bottom: 1px solid #f2f2f2; + margin-bottom: 10px; + padding-bottom: 10px; +} + +.widget-layout #brain_buster_captcha { + +} + +// specific elements +.widget-layout #discussion_body { + margin: 0 0 15px 0; +} + +.widget-layout .category dt, .widget-layout .category dd { + display: inline-block !important; +} + +.widget-layout .category dt { + margin-right: 15px !important; +} \ No newline at end of file From 24301d2a0761510143f7bc62bc9d7d0d01abd5ca Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 11:30:31 -0400 Subject: [PATCH 068/483] Moved helper functions from terrain/steps.py to terrain/helpers.py --- common/djangoapps/terrain/helpers.py | 152 +++++++++++++++++++++++++ common/djangoapps/terrain/steps.py | 164 ++------------------------- 2 files changed, 159 insertions(+), 157 deletions(-) create mode 100644 common/djangoapps/terrain/helpers.py diff --git a/common/djangoapps/terrain/helpers.py b/common/djangoapps/terrain/helpers.py new file mode 100644 index 0000000000..55c8f3db5a --- /dev/null +++ b/common/djangoapps/terrain/helpers.py @@ -0,0 +1,152 @@ +from lettuce import world, step +from .factories import * +from django.conf import settings +from django.http import HttpRequest +from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login +from django.contrib.auth.middleware import AuthenticationMiddleware +from django.contrib.sessions.middleware import SessionMiddleware +from student.models import CourseEnrollment +from bs4 import BeautifulSoup +import os.path +from selenium.common.exceptions import WebDriverException +from urllib import quote_plus +from lettuce.django import django_url + +@world.absorb +def wait(seconds): + time.sleep(float(seconds)) + +@world.absorb +def scroll_to_bottom(): + # Maximize the browser + world.browser.execute_script("window.scrollTo(0, screen.height);") + + +@world.absorb +def create_user(uname): + + # If the user already exists, don't try to create it again + if len(User.objects.filter(username=uname)) > 0: + return + + portal_user = UserFactory.build(username=uname, email=uname + '@edx.org') + portal_user.set_password('test') + portal_user.save() + + registration = world.RegistrationFactory(user=portal_user) + registration.register(portal_user) + registration.activate() + + user_profile = world.UserProfileFactory(user=portal_user) + + +@world.absorb +def log_in(username, password): + ''' + Log the user in programatically + ''' + + # Authenticate the user + user = authenticate(username=username, password=password) + assert(user is not None and user.is_active) + + # Send a fake HttpRequest to log the user in + # We need to process the request using + # Session middleware and Authentication middleware + # to ensure that session state can be stored + request = HttpRequest() + SessionMiddleware().process_request(request) + AuthenticationMiddleware().process_request(request) + login(request, user) + + # Save the session + request.session.save() + + # Retrieve the sessionid and add it to the browser's cookies + cookie_dict = {settings.SESSION_COOKIE_NAME: request.session.session_key} + try: + world.browser.cookies.add(cookie_dict) + + # WebDriver has an issue where we cannot set cookies + # before we make a GET request, so if we get an error, + # we load the '/' page and try again + except: + world.browser.visit(django_url('/')) + world.browser.cookies.add(cookie_dict) + + +@world.absorb +def register_by_course_id(course_id, is_staff=False): + create_user('robot') + u = User.objects.get(username='robot') + if is_staff: + u.is_staff = True + u.save() + CourseEnrollment.objects.get_or_create(user=u, course_id=course_id) + + +@world.absorb +def save_the_html(path='/tmp'): + u = world.browser.url + html = world.browser.html.encode('ascii', 'ignore') + filename = '%s.html' % quote_plus(u) + f = open('%s/%s' % (path, filename), 'w') + f.write(html) + f.close + + +@world.absorb +def save_the_course_content(path='/tmp'): + html = world.browser.html.encode('ascii', 'ignore') + soup = BeautifulSoup(html) + + # get rid of the header, we only want to compare the body + soup.head.decompose() + + # for now, remove the data-id attributes, because they are + # causing mismatches between cms-master and master + for item in soup.find_all(attrs={'data-id': re.compile('.*')}): + del item['data-id'] + + # we also need to remove them from unrendered problems, + # where they are contained in the text of divs instead of + # in attributes of tags + # Be careful of whether or not it was the last attribute + # and needs a trailing space + for item in soup.find_all(text=re.compile(' data-id=".*?" ')): + s = unicode(item.string) + item.string.replace_with(re.sub(' data-id=".*?" ', ' ', s)) + + for item in soup.find_all(text=re.compile(' data-id=".*?"')): + s = unicode(item.string) + item.string.replace_with(re.sub(' data-id=".*?"', ' ', s)) + + # prettify the html so it will compare better, with + # each HTML tag on its own line + output = soup.prettify() + + # use string slicing to grab everything after 'courseware/' in the URL + u = world.browser.url + section_url = u[u.find('courseware/') + 11:] + + + if not os.path.exists(path): + os.makedirs(path) + + filename = '%s.html' % (quote_plus(section_url)) + f = open('%s/%s' % (path, filename), 'w') + f.write(output) + f.close + +@world.absorb +def css_click(css_selector): + try: + world.browser.find_by_css(css_selector).click() + + except WebDriverException: + # Occassionally, MathJax or other JavaScript can cover up + # an element temporarily. + # If this happens, wait a second, then try again + time.sleep(1) + world.browser.find_by_css(css_selector).click() diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index 3bc838a6af..ae36227fee 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -1,20 +1,8 @@ from lettuce import world, step -from .factories import * +from .helpers import * from lettuce.django import django_url -from django.conf import settings -from django.http import HttpRequest -from django.contrib.auth.models import User -from django.contrib.auth import authenticate, login -from django.contrib.auth.middleware import AuthenticationMiddleware -from django.contrib.sessions.middleware import SessionMiddleware -from student.models import CourseEnrollment -from urllib import quote_plus from nose.tools import assert_equals -from bs4 import BeautifulSoup import time -import re -import os.path -from selenium.common.exceptions import WebDriverException from logging import getLogger logger = getLogger(__name__) @@ -22,8 +10,7 @@ logger = getLogger(__name__) @step(u'I wait (?:for )?"(\d+)" seconds?$') def wait(step, seconds): - time.sleep(float(seconds)) - + world.wait(seconds) @step('I reload the page$') def reload_the_page(step): @@ -87,8 +74,8 @@ def the_page_title_should_contain(step, title): @step('I am a logged in user$') def i_am_logged_in_user(step): - create_user('robot') - log_in('robot', 'test') + world.create_user('robot') + world.log_in('robot', 'test') @step('I am not logged in$') @@ -98,151 +85,14 @@ def i_am_not_logged_in(step): @step('I am staff for course "([^"]*)"$') def i_am_staff_for_course_by_id(step, course_id): - register_by_course_id(course_id, True) + world.register_by_course_id(course_id, True) @step('I log in$') def i_log_in(step): - log_in('robot', 'test') + world.log_in('robot', 'test') @step(u'I am an edX user$') def i_am_an_edx_user(step): - create_user('robot') - -#### helper functions - - -@world.absorb -def scroll_to_bottom(): - # Maximize the browser - world.browser.execute_script("window.scrollTo(0, screen.height);") - - -@world.absorb -def create_user(uname): - - # If the user already exists, don't try to create it again - if len(User.objects.filter(username=uname)) > 0: - return - - portal_user = UserFactory.build(username=uname, email=uname + '@edx.org') - portal_user.set_password('test') - portal_user.save() - - registration = world.RegistrationFactory(user=portal_user) - registration.register(portal_user) - registration.activate() - - user_profile = world.UserProfileFactory(user=portal_user) - - -@world.absorb -def log_in(username, password): - ''' - Log the user in programatically - ''' - - # Authenticate the user - user = authenticate(username=username, password=password) - assert(user is not None and user.is_active) - - # Send a fake HttpRequest to log the user in - # We need to process the request using - # Session middleware and Authentication middleware - # to ensure that session state can be stored - request = HttpRequest() - SessionMiddleware().process_request(request) - AuthenticationMiddleware().process_request(request) - login(request, user) - - # Save the session - request.session.save() - - # Retrieve the sessionid and add it to the browser's cookies - cookie_dict = {settings.SESSION_COOKIE_NAME: request.session.session_key} - try: - world.browser.cookies.add(cookie_dict) - - # WebDriver has an issue where we cannot set cookies - # before we make a GET request, so if we get an error, - # we load the '/' page and try again - except: - world.browser.visit(django_url('/')) - world.browser.cookies.add(cookie_dict) - - -@world.absorb -def register_by_course_id(course_id, is_staff=False): - create_user('robot') - u = User.objects.get(username='robot') - if is_staff: - u.is_staff = True - u.save() - CourseEnrollment.objects.get_or_create(user=u, course_id=course_id) - - -@world.absorb -def save_the_html(path='/tmp'): - u = world.browser.url - html = world.browser.html.encode('ascii', 'ignore') - filename = '%s.html' % quote_plus(u) - f = open('%s/%s' % (path, filename), 'w') - f.write(html) - f.close - - -@world.absorb -def save_the_course_content(path='/tmp'): - html = world.browser.html.encode('ascii', 'ignore') - soup = BeautifulSoup(html) - - # get rid of the header, we only want to compare the body - soup.head.decompose() - - # for now, remove the data-id attributes, because they are - # causing mismatches between cms-master and master - for item in soup.find_all(attrs={'data-id': re.compile('.*')}): - del item['data-id'] - - # we also need to remove them from unrendered problems, - # where they are contained in the text of divs instead of - # in attributes of tags - # Be careful of whether or not it was the last attribute - # and needs a trailing space - for item in soup.find_all(text=re.compile(' data-id=".*?" ')): - s = unicode(item.string) - item.string.replace_with(re.sub(' data-id=".*?" ', ' ', s)) - - for item in soup.find_all(text=re.compile(' data-id=".*?"')): - s = unicode(item.string) - item.string.replace_with(re.sub(' data-id=".*?"', ' ', s)) - - # prettify the html so it will compare better, with - # each HTML tag on its own line - output = soup.prettify() - - # use string slicing to grab everything after 'courseware/' in the URL - u = world.browser.url - section_url = u[u.find('courseware/') + 11:] - - - if not os.path.exists(path): - os.makedirs(path) - - filename = '%s.html' % (quote_plus(section_url)) - f = open('%s/%s' % (path, filename), 'w') - f.write(output) - f.close - -@world.absorb -def css_click(css_selector): - try: - world.browser.find_by_css(css_selector).click() - - except WebDriverException: - # Occassionally, MathJax or other JavaScript can cover up - # an element temporarily. - # If this happens, wait a second, then try again - time.sleep(1) - world.browser.find_by_css(css_selector).click() + world.create_user('robot') From 315b360e4cafeab3fec798272ed2e5ee22cb88d0 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 11:31:41 -0400 Subject: [PATCH 069/483] Fixed an import error in terrain/helpers.py --- common/djangoapps/terrain/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/djangoapps/terrain/helpers.py b/common/djangoapps/terrain/helpers.py index 55c8f3db5a..12d6818659 100644 --- a/common/djangoapps/terrain/helpers.py +++ b/common/djangoapps/terrain/helpers.py @@ -12,6 +12,7 @@ import os.path from selenium.common.exceptions import WebDriverException from urllib import quote_plus from lettuce.django import django_url +import time @world.absorb def wait(seconds): From e494d529fc48f21c1bb01bdee7dc8515035b6219 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 11:38:30 -0400 Subject: [PATCH 070/483] Split terrain/helpers.py into ui_helpers.py and course_helpers.py --- .../terrain/{helpers.py => course_helpers.py} | 32 ------------------- common/djangoapps/terrain/steps.py | 3 +- common/djangoapps/terrain/ui_helpers.py | 30 +++++++++++++++++ 3 files changed, 32 insertions(+), 33 deletions(-) rename common/djangoapps/terrain/{helpers.py => course_helpers.py} (82%) create mode 100644 common/djangoapps/terrain/ui_helpers.py diff --git a/common/djangoapps/terrain/helpers.py b/common/djangoapps/terrain/course_helpers.py similarity index 82% rename from common/djangoapps/terrain/helpers.py rename to common/djangoapps/terrain/course_helpers.py index 12d6818659..dbdaa2a21c 100644 --- a/common/djangoapps/terrain/helpers.py +++ b/common/djangoapps/terrain/course_helpers.py @@ -12,17 +12,6 @@ import os.path from selenium.common.exceptions import WebDriverException from urllib import quote_plus from lettuce.django import django_url -import time - -@world.absorb -def wait(seconds): - time.sleep(float(seconds)) - -@world.absorb -def scroll_to_bottom(): - # Maximize the browser - world.browser.execute_script("window.scrollTo(0, screen.height);") - @world.absorb def create_user(uname): @@ -87,15 +76,6 @@ def register_by_course_id(course_id, is_staff=False): CourseEnrollment.objects.get_or_create(user=u, course_id=course_id) -@world.absorb -def save_the_html(path='/tmp'): - u = world.browser.url - html = world.browser.html.encode('ascii', 'ignore') - filename = '%s.html' % quote_plus(u) - f = open('%s/%s' % (path, filename), 'w') - f.write(html) - f.close - @world.absorb def save_the_course_content(path='/tmp'): @@ -139,15 +119,3 @@ def save_the_course_content(path='/tmp'): f = open('%s/%s' % (path, filename), 'w') f.write(output) f.close - -@world.absorb -def css_click(css_selector): - try: - world.browser.find_by_css(css_selector).click() - - except WebDriverException: - # Occassionally, MathJax or other JavaScript can cover up - # an element temporarily. - # If this happens, wait a second, then try again - time.sleep(1) - world.browser.find_by_css(css_selector).click() diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index ae36227fee..6e54b71aa6 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -1,5 +1,6 @@ from lettuce import world, step -from .helpers import * +from .course_helpers import * +from .ui_helpers import * from lettuce.django import django_url from nose.tools import assert_equals import time diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py new file mode 100644 index 0000000000..4667957e87 --- /dev/null +++ b/common/djangoapps/terrain/ui_helpers.py @@ -0,0 +1,30 @@ +from lettuce import world, step +import time +from urllib import quote_plus + +@world.absorb +def wait(seconds): + time.sleep(float(seconds)) + + +@world.absorb +def css_click(css_selector): + try: + world.browser.find_by_css(css_selector).click() + + except WebDriverException: + # Occassionally, MathJax or other JavaScript can cover up + # an element temporarily. + # If this happens, wait a second, then try again + time.sleep(1) + world.browser.find_by_css(css_selector).click() + +@world.absorb +def save_the_html(path='/tmp'): + u = world.browser.url + html = world.browser.html.encode('ascii', 'ignore') + filename = '%s.html' % quote_plus(u) + f = open('%s/%s' % (path, filename), 'w') + f.write(html) + f.close + From 0562f11c5622c94214162ac5c43fd69b8851601f Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 11:41:30 -0400 Subject: [PATCH 071/483] Fixed import issue with WebDriverException --- common/djangoapps/terrain/course_helpers.py | 1 - common/djangoapps/terrain/ui_helpers.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/terrain/course_helpers.py b/common/djangoapps/terrain/course_helpers.py index dbdaa2a21c..8c949de1ad 100644 --- a/common/djangoapps/terrain/course_helpers.py +++ b/common/djangoapps/terrain/course_helpers.py @@ -9,7 +9,6 @@ from django.contrib.sessions.middleware import SessionMiddleware from student.models import CourseEnrollment from bs4 import BeautifulSoup import os.path -from selenium.common.exceptions import WebDriverException from urllib import quote_plus from lettuce.django import django_url diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 4667957e87..2ad7150740 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -1,6 +1,7 @@ from lettuce import world, step import time from urllib import quote_plus +from selenium.common.exceptions import WebDriverException @world.absorb def wait(seconds): From b0eb73302b9753acbc53f3ddc4fe86226f51292b Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 11:50:50 -0400 Subject: [PATCH 072/483] Moved some courseware/features/common.py steps into terrain/steps.py --- common/djangoapps/terrain/steps.py | 38 ++++++++- lms/djangoapps/courseware/features/common.py | 83 -------------------- 2 files changed, 36 insertions(+), 85 deletions(-) diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index 6e54b71aa6..8356b5446d 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -72,6 +72,9 @@ def the_page_title_should_be(step, title): def the_page_title_should_contain(step, title): assert(title in world.browser.title) +@step('I log in$') +def i_log_in(step): + world.log_in('robot', 'test') @step('I am a logged in user$') def i_am_logged_in_user(step): @@ -89,11 +92,42 @@ def i_am_staff_for_course_by_id(step, course_id): world.register_by_course_id(course_id, True) -@step('I log in$') -def i_log_in(step): +@step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$') +def click_the_link_called(step, text): + world.browser.find_link_by_text(text).click() + + +@step(r'should see that the url is "([^"]*)"$') +def should_have_the_url(step, url): + assert_equals(world.browser.url, url) + +@step(r'should see (?:the|a) link (?:called|with the text) "([^"]*)"$') +def should_see_a_link_called(step, text): + assert len(world.browser.find_link_by_text(text)) > 0 + +@step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page') +def should_see_in_the_page(step, text): + assert_in(text, world.browser.html) + + +@step('I am logged in$') +def i_am_logged_in(step): + world.create_user('robot') world.log_in('robot', 'test') + world.browser.visit(django_url('/')) + + +@step('I am not logged in$') +def i_am_not_logged_in(step): + world.browser.cookies.delete() @step(u'I am an edX user$') def i_am_an_edx_user(step): world.create_user('robot') + + +@step(u'User "([^"]*)" is an edX user$') +def registered_edx_user(step, uname): + world.create_user(uname) + diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index 7d41637c8e..8477347580 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -6,83 +6,10 @@ from student.models import CourseEnrollment from xmodule.modulestore import Location from xmodule.modulestore.django import _MODULESTORES, modulestore from xmodule.templates import update_templates -import time from logging import getLogger logger = getLogger(__name__) - -@step(u'I wait (?:for )?"(\d+)" seconds?$') -def wait(step, seconds): - time.sleep(float(seconds)) - - -@step('I (?:visit|access|open) the homepage$') -def i_visit_the_homepage(step): - world.browser.visit(django_url('/')) - assert world.browser.is_element_present_by_css('header.global', 10) - - -@step(u'I (?:visit|access|open) the dashboard$') -def i_visit_the_dashboard(step): - world.browser.visit(django_url('/dashboard')) - assert world.browser.is_element_present_by_css('section.container.dashboard', 5) - - -@step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$') -def click_the_link_called(step, text): - world.browser.find_link_by_text(text).click() - - -@step('I should be on the dashboard page$') -def i_should_be_on_the_dashboard(step): - assert world.browser.is_element_present_by_css('section.container.dashboard', 5) - assert world.browser.title == 'Dashboard' - - -@step(u'I (?:visit|access|open) the courses page$') -def i_am_on_the_courses_page(step): - world.browser.visit(django_url('/courses')) - assert world.browser.is_element_present_by_css('section.courses') - - -@step('I should see that the path is "([^"]*)"$') -def i_should_see_that_the_path_is(step, path): - assert world.browser.url == django_url(path) - - -@step(u'the page title should be "([^"]*)"$') -def the_page_title_should_be(step, title): - assert world.browser.title == title - - -@step(r'should see that the url is "([^"]*)"$') -def should_have_the_url(step, url): - assert_equals(world.browser.url, url) - - -@step(r'should see (?:the|a) link (?:called|with the text) "([^"]*)"$') -def should_see_a_link_called(step, text): - assert len(world.browser.find_link_by_text(text)) > 0 - - -@step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page') -def should_see_in_the_page(step, text): - assert_in(text, world.browser.html) - - -@step('I am logged in$') -def i_am_logged_in(step): - world.create_user('robot') - world.log_in('robot', 'test') - world.browser.visit(django_url('/')) - - -@step('I am not logged in$') -def i_am_not_logged_in(step): - world.browser.cookies.delete() - - TEST_COURSE_ORG = 'edx' TEST_COURSE_NAME = 'Test Course' TEST_SECTION_NAME = "Problem" @@ -135,16 +62,6 @@ def add_tab_to_course(step, course, extra_tab_name): display_name=str(extra_tab_name)) -@step(u'I am an edX user$') -def i_am_an_edx_user(step): - world.create_user('robot') - - -@step(u'User "([^"]*)" is an edX user$') -def registered_edx_user(step, uname): - world.create_user(uname) - - def flush_xmodule_store(): # Flush and initialize the module store # It needs the templates because it creates new records From c12e1fb1cec0fabd3d825dc7f270381146b1a2e7 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 11:54:17 -0400 Subject: [PATCH 073/483] Added missing import statement --- common/djangoapps/terrain/steps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index 8356b5446d..8dac372a64 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -2,7 +2,7 @@ from lettuce import world, step from .course_helpers import * from .ui_helpers import * from lettuce.django import django_url -from nose.tools import assert_equals +from nose.tools import assert_equals, assert_in import time from logging import getLogger From 5e69050a163fc19e6ce042b206e8a25f105ac509 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 12:01:55 -0400 Subject: [PATCH 074/483] Elminated unused functions from courseware/features/courses.py and moved the rest to common.py --- lms/djangoapps/courseware/features/common.py | 87 +++++++ lms/djangoapps/courseware/features/courses.py | 234 ------------------ .../courseware/features/smart-accordion.py | 2 +- 3 files changed, 88 insertions(+), 235 deletions(-) delete mode 100644 lms/djangoapps/courseware/features/courses.py diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index 8477347580..2d366d462d 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -6,6 +6,9 @@ from student.models import CourseEnrollment from xmodule.modulestore import Location from xmodule.modulestore.django import _MODULESTORES, modulestore from xmodule.templates import update_templates +from xmodule.course_module import CourseDescriptor +from courseware.courses import get_course_by_id +from xmodule import seq_module, vertical_module from logging import getLogger logger = getLogger(__name__) @@ -94,3 +97,87 @@ def section_location(course_num): course=course_num, category='sequential', name=TEST_SECTION_NAME.replace(" ", "_")) + + +def get_courses(): + ''' + Returns dict of lists of courses available, keyed by course.org (ie university). + Courses are sorted by course.number. + ''' + courses = [c for c in modulestore().get_courses() + if isinstance(c, CourseDescriptor)] + courses = sorted(courses, key=lambda course: course.number) + return courses + + +def get_courseware_with_tabs(course_id): + """ + Given a course_id (string), return a courseware array of dictionaries for the + top three levels of navigation. Same as get_courseware() except include + the tabs on the right hand main navigation page. + + This hides the appropriate courseware as defined by the hide_from_toc field: + chapter.lms.hide_from_toc + + Example: + + [{ + 'chapter_name': 'Overview', + 'sections': [{ + 'clickable_tab_count': 0, + 'section_name': 'Welcome', + 'tab_classes': [] + }, { + 'clickable_tab_count': 1, + 'section_name': 'System Usage Sequence', + 'tab_classes': ['VerticalDescriptor'] + }, { + 'clickable_tab_count': 0, + 'section_name': 'Lab0: Using the tools', + 'tab_classes': ['HtmlDescriptor', 'HtmlDescriptor', 'CapaDescriptor'] + }, { + 'clickable_tab_count': 0, + 'section_name': 'Circuit Sandbox', + 'tab_classes': [] + }] + }, { + 'chapter_name': 'Week 1', + 'sections': [{ + 'clickable_tab_count': 4, + 'section_name': 'Administrivia and Circuit Elements', + 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor'] + }, { + 'clickable_tab_count': 0, + 'section_name': 'Basic Circuit Analysis', + 'tab_classes': ['CapaDescriptor', 'CapaDescriptor', 'CapaDescriptor'] + }, { + 'clickable_tab_count': 0, + 'section_name': 'Resistor Divider', + 'tab_classes': [] + }, { + 'clickable_tab_count': 0, + 'section_name': 'Week 1 Tutorials', + 'tab_classes': [] + }] + }, { + 'chapter_name': 'Midterm Exam', + 'sections': [{ + 'clickable_tab_count': 2, + 'section_name': 'Midterm Exam', + 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor'] + }] + }] + """ + + course = get_course_by_id(course_id) + chapters = [chapter for chapter in course.get_children() if not chapter.lms.hide_from_toc] + courseware = [{'chapter_name': c.display_name_with_default, + 'sections': [{'section_name': s.display_name_with_default, + 'clickable_tab_count': len(s.get_children()) if (type(s) == seq_module.SequenceDescriptor) else 0, + 'tabs': [{'children_count': len(t.get_children()) if (type(t) == vertical_module.VerticalDescriptor) else 0, + 'class': t.__class__.__name__} + for t in s.get_children()]} + for s in c.get_children() if not s.lms.hide_from_toc]} + for c in chapters] + + return courseware diff --git a/lms/djangoapps/courseware/features/courses.py b/lms/djangoapps/courseware/features/courses.py deleted file mode 100644 index c99fb58b85..0000000000 --- a/lms/djangoapps/courseware/features/courses.py +++ /dev/null @@ -1,234 +0,0 @@ -from lettuce import world -from xmodule.course_module import CourseDescriptor -from xmodule.modulestore.django import modulestore -from courseware.courses import get_course_by_id -from xmodule import seq_module, vertical_module - -from logging import getLogger -logger = getLogger(__name__) - -## support functions - - -def get_courses(): - ''' - Returns dict of lists of courses available, keyed by course.org (ie university). - Courses are sorted by course.number. - ''' - courses = [c for c in modulestore().get_courses() - if isinstance(c, CourseDescriptor)] - courses = sorted(courses, key=lambda course: course.number) - return courses - - -def get_courseware_with_tabs(course_id): - """ - Given a course_id (string), return a courseware array of dictionaries for the - top three levels of navigation. Same as get_courseware() except include - the tabs on the right hand main navigation page. - - This hides the appropriate courseware as defined by the hide_from_toc field: - chapter.lms.hide_from_toc - - Example: - - [{ - 'chapter_name': 'Overview', - 'sections': [{ - 'clickable_tab_count': 0, - 'section_name': 'Welcome', - 'tab_classes': [] - }, { - 'clickable_tab_count': 1, - 'section_name': 'System Usage Sequence', - 'tab_classes': ['VerticalDescriptor'] - }, { - 'clickable_tab_count': 0, - 'section_name': 'Lab0: Using the tools', - 'tab_classes': ['HtmlDescriptor', 'HtmlDescriptor', 'CapaDescriptor'] - }, { - 'clickable_tab_count': 0, - 'section_name': 'Circuit Sandbox', - 'tab_classes': [] - }] - }, { - 'chapter_name': 'Week 1', - 'sections': [{ - 'clickable_tab_count': 4, - 'section_name': 'Administrivia and Circuit Elements', - 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor', 'VerticalDescriptor'] - }, { - 'clickable_tab_count': 0, - 'section_name': 'Basic Circuit Analysis', - 'tab_classes': ['CapaDescriptor', 'CapaDescriptor', 'CapaDescriptor'] - }, { - 'clickable_tab_count': 0, - 'section_name': 'Resistor Divider', - 'tab_classes': [] - }, { - 'clickable_tab_count': 0, - 'section_name': 'Week 1 Tutorials', - 'tab_classes': [] - }] - }, { - 'chapter_name': 'Midterm Exam', - 'sections': [{ - 'clickable_tab_count': 2, - 'section_name': 'Midterm Exam', - 'tab_classes': ['VerticalDescriptor', 'VerticalDescriptor'] - }] - }] - """ - - course = get_course_by_id(course_id) - chapters = [chapter for chapter in course.get_children() if not chapter.lms.hide_from_toc] - courseware = [{'chapter_name': c.display_name_with_default, - 'sections': [{'section_name': s.display_name_with_default, - 'clickable_tab_count': len(s.get_children()) if (type(s) == seq_module.SequenceDescriptor) else 0, - 'tabs': [{'children_count': len(t.get_children()) if (type(t) == vertical_module.VerticalDescriptor) else 0, - 'class': t.__class__.__name__} - for t in s.get_children()]} - for s in c.get_children() if not s.lms.hide_from_toc]} - for c in chapters] - - return courseware - - -def process_section(element, num_tabs=0): - ''' - Process section reads through whatever is in 'course-content' and classifies it according to sequence module type. - - This function is recursive - - There are 6 types, with 6 actions. - - Sequence Module - -contains one child module - - Vertical Module - -contains other modules - -process it and get its children, then process them - - Capa Module - -problem type, contains only one problem - -for this, the most complex type, we created a separate method, process_problem - - Video Module - -video type, contains only one video - -we only check to ensure that a section with class of video exists - - HTML Module - -html text - -we do not check anything about it - - Custom Tag Module - -a custom 'hack' module type - -there is a large variety of content that could go in a custom tag module, so we just pass if it is of this unusual type - - can be used like this: - e = world.browser.find_by_css('section.course-content section') - process_section(e) - - ''' - if element.has_class('xmodule_display xmodule_SequenceModule'): - logger.debug('####### Processing xmodule_SequenceModule') - child_modules = element.find_by_css("div>div>section[class^='xmodule']") - for mod in child_modules: - process_section(mod) - - elif element.has_class('xmodule_display xmodule_VerticalModule'): - logger.debug('####### Processing xmodule_VerticalModule') - vert_list = element.find_by_css("li section[class^='xmodule']") - for item in vert_list: - process_section(item) - - elif element.has_class('xmodule_display xmodule_CapaModule'): - logger.debug('####### Processing xmodule_CapaModule') - assert element.find_by_css("section[id^='problem']"), "No problems found in Capa Module" - p = element.find_by_css("section[id^='problem']").first - p_id = p['id'] - logger.debug('####################') - logger.debug('id is "%s"' % p_id) - logger.debug('####################') - process_problem(p, p_id) - - elif element.has_class('xmodule_display xmodule_VideoModule'): - logger.debug('####### Processing xmodule_VideoModule') - assert element.find_by_css("section[class^='video']"), "No video found in Video Module" - - elif element.has_class('xmodule_display xmodule_HtmlModule'): - logger.debug('####### Processing xmodule_HtmlModule') - pass - - elif element.has_class('xmodule_display xmodule_CustomTagModule'): - logger.debug('####### Processing xmodule_CustomTagModule') - pass - - else: - assert False, "Class for element not recognized!!" - - -def process_problem(element, problem_id): - ''' - Process problem attempts to - 1) scan all the input fields and reset them - 2) click the 'check' button and look for an incorrect response (p.status text should be 'incorrect') - 3) click the 'show answer' button IF it exists and IF the answer is not already displayed - 4) enter the correct answer in each input box - 5) click the 'check' button and verify that answers are correct - - Because of all the ajax calls happening, sometimes the test fails because objects disconnect from the DOM. - The basic functionality does exist, though, and I'm hoping that someone can take it over and make it super effective. - ''' - - prob_xmod = element.find_by_css("section.problem").first - input_fields = prob_xmod.find_by_css("section[id^='input']") - - ## clear out all input to ensure an incorrect result - for field in input_fields: - field.find_by_css("input").first.fill('') - - ## because of cookies or the application, only click the 'check' button if the status is not already 'incorrect' - # This would need to be reworked because multiple choice problems don't have this status - # if prob_xmod.find_by_css("p.status").first.text.strip().lower() != 'incorrect': - prob_xmod.find_by_css("section.action input.check").first.click() - - ## all elements become disconnected after the click - ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy) - # Wait for the ajax reload - assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5) - element = world.browser.find_by_css("section[id='%s']" % problem_id).first - prob_xmod = element.find_by_css("section.problem").first - input_fields = prob_xmod.find_by_css("section[id^='input']") - for field in input_fields: - assert field.find_by_css("div.incorrect"), "The 'check' button did not work for %s" % (problem_id) - - show_button = element.find_by_css("section.action input.show").first - ## this logic is to ensure we do not accidentally hide the answers - if show_button.value.lower() == 'show answer': - show_button.click() - else: - pass - - ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy) - assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5) - element = world.browser.find_by_css("section[id='%s']" % problem_id).first - prob_xmod = element.find_by_css("section.problem").first - input_fields = prob_xmod.find_by_css("section[id^='input']") - - ## in each field, find the answer, and send it to the field. - ## Note that this does not work if the answer type is a strange format, e.g. "either a or b" - for field in input_fields: - field.find_by_css("input").first.fill(field.find_by_css("p[id^='answer']").first.text) - - prob_xmod.find_by_css("section.action input.check").first.click() - - ## assert that we entered the correct answers - ## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy) - assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5) - element = world.browser.find_by_css("section[id='%s']" % problem_id).first - prob_xmod = element.find_by_css("section.problem").first - input_fields = prob_xmod.find_by_css("section[id^='input']") - for field in input_fields: - ## if you don't use 'starts with ^=' the test will fail because the actual class is 'correct ' (with a space) - assert field.find_by_css("div[class^='correct']"), "The check answer values were not correct for %s" % problem_id diff --git a/lms/djangoapps/courseware/features/smart-accordion.py b/lms/djangoapps/courseware/features/smart-accordion.py index a7eb782722..539bce96ce 100644 --- a/lms/djangoapps/courseware/features/smart-accordion.py +++ b/lms/djangoapps/courseware/features/smart-accordion.py @@ -2,7 +2,7 @@ from lettuce import world, step from re import sub from nose.tools import assert_equals from xmodule.modulestore.django import modulestore -from courses import * +from common import * from logging import getLogger logger = getLogger(__name__) From 6dd86f7a97826ec7af6fcb608928d6f0a7c07660 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 12:19:46 -0400 Subject: [PATCH 075/483] Refactored courseware_common and open_ended to use ui helpers --- common/djangoapps/terrain/ui_helpers.py | 16 +++++++++ .../courseware/features/courseware_common.py | 15 +++----- .../courseware/features/openended.py | 36 +++++++------------ 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 2ad7150740..d56ce3649b 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -20,6 +20,22 @@ def css_click(css_selector): time.sleep(1) world.browser.find_by_css(css_selector).click() +@world.absorb +def css_fill(css_selector, text): + world.browser.find_by_css(css_selector).first.fill(text) + +@world.absorb +def click_link(partial_text): + world.browser.find_link_by_partial_text(partial_text).first.click() + +@world.absorb +def css_text(css_selector): + return world.browser.find_by_css(css_selector).first.text + +@world.absorb +def css_visible(css_selector): + return world.browser.find_by_css(css_selector).visible + @world.absorb def save_the_html(path='/tmp'): u = world.browser.url diff --git a/lms/djangoapps/courseware/features/courseware_common.py b/lms/djangoapps/courseware/features/courseware_common.py index 96304e016f..567254c334 100644 --- a/lms/djangoapps/courseware/features/courseware_common.py +++ b/lms/djangoapps/courseware/features/courseware_common.py @@ -9,11 +9,10 @@ def i_click_on_view_courseware(step): @step('I click on the "([^"]*)" tab$') -def i_click_on_the_tab(step, tab): - world.browser.find_link_by_partial_text(tab).first.click() +def i_click_on_the_tab(step, tab_text): + world.click_link(tab_text) world.save_the_html() - @step('I visit the courseware URL$') def i_visit_the_course_info_url(step): url = django_url('/courses/MITx/6.002x/2012_Fall/courseware') @@ -32,13 +31,9 @@ def i_am_on_the_dashboard_page(step): @step('the "([^"]*)" tab is active$') -def the_tab_is_active(step, tab): - css = '.course-tabs a.active' - active_tab = world.browser.find_by_css(css) - assert (active_tab.text == tab) - +def the_tab_is_active(step, tab_text): + assert world.css_text('.course-tabs a.active') == tab_text @step('the login dialog is visible$') def login_dialog_visible(step): - css = 'form#login_form.login_form' - assert world.browser.find_by_css(css).visible + assert world.css_visible('form#login_form.login_form') diff --git a/lms/djangoapps/courseware/features/openended.py b/lms/djangoapps/courseware/features/openended.py index 0725a051ff..7601bfcc53 100644 --- a/lms/djangoapps/courseware/features/openended.py +++ b/lms/djangoapps/courseware/features/openended.py @@ -12,7 +12,7 @@ def navigate_to_an_openended_question(step): problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/' world.browser.visit(django_url(problem)) tab_css = 'ol#sequence-list > li > a[data-element="5"]' - world.browser.find_by_css(tab_css).click() + world.css_click(tab_css) @step('I navigate to an openended question as staff$') @@ -22,50 +22,41 @@ def navigate_to_an_openended_question_as_staff(step): problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/' world.browser.visit(django_url(problem)) tab_css = 'ol#sequence-list > li > a[data-element="5"]' - world.browser.find_by_css(tab_css).click() + world.css_click(tab_css) @step(u'I enter the answer "([^"]*)"$') def enter_the_answer_text(step, text): - textarea_css = 'textarea' - world.browser.find_by_css(textarea_css).first.fill(text) + world.css_fill('textarea', text) @step(u'I submit the answer "([^"]*)"$') def i_submit_the_answer_text(step, text): - textarea_css = 'textarea' - world.browser.find_by_css(textarea_css).first.fill(text) - check_css = 'input.check' - world.browser.find_by_css(check_css).click() + world.css_fill('textarea', text) + world.css_click('input.check') @step('I click the link for full output$') def click_full_output_link(step): - link_css = 'a.full' - world.browser.find_by_css(link_css).first.click() + world.css_click('a.full') @step(u'I visit the staff grading page$') def i_visit_the_staff_grading_page(step): - # course_u = '/courses/MITx/3.091x/2012_Fall' - # sg_url = '%s/staff_grading' % course_u - world.browser.click_link_by_text('Instructor') - world.browser.click_link_by_text('Staff grading') - # world.browser.visit(django_url(sg_url)) + world.click_link('Instructor') + world.click_link('Staff grading') @step(u'I see the grader message "([^"]*)"$') def see_grader_message(step, msg): message_css = 'div.external-grader-message' - grader_msg = world.browser.find_by_css(message_css).text - assert_in(msg, grader_msg) + assert_in(msg, world.css_text(message_css)) @step(u'I see the grader status "([^"]*)"$') def see_the_grader_status(step, status): status_css = 'div.grader-status' - grader_status = world.browser.find_by_css(status_css).text - assert_equals(status, grader_status) + assert_equals(status, world.css_text(status_css)) @step('I see the red X$') @@ -77,7 +68,7 @@ def see_the_red_x(step): @step(u'I see the grader score "([^"]*)"$') def see_the_grader_score(step, score): score_css = 'div.result-output > p' - score_text = world.browser.find_by_css(score_css).text + score_text = world.css_text(score_css) assert_equals(score_text, 'Score: %s' % score) @@ -89,14 +80,13 @@ def see_full_output_link(step): @step('I see the spelling grading message "([^"]*)"$') def see_spelling_msg(step, msg): - spelling_css = 'div.spelling' - spelling_msg = world.browser.find_by_css(spelling_css).text + spelling_msg = world.css_text('div.spelling') assert_equals('Spelling: %s' % msg, spelling_msg) @step(u'my answer is queued for instructor grading$') def answer_is_queued_for_instructor_grading(step): list_css = 'ul.problem-list > li > a' - actual_msg = world.browser.find_by_css(list_css).text + actual_msg = world.css_text(list_css) expected_msg = "(0 graded, 1 pending)" assert_in(expected_msg, actual_msg) From 4528490fac9050881eba0ff98df07782e71bbabc Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 12:40:33 -0400 Subject: [PATCH 076/483] Refactored lms/coureware lettuce tests to use terrain helpers for common ui manipulations --- common/djangoapps/terrain/course_helpers.py | 1 + common/djangoapps/terrain/steps.py | 6 ++++- common/djangoapps/terrain/ui_helpers.py | 23 ++++++++++++++++++- .../courseware/features/courseware_common.py | 13 +++++------ lms/djangoapps/courseware/features/login.py | 4 +--- .../courseware/features/openended.py | 6 ++--- .../courseware/features/problems.py | 4 ++-- .../courseware/features/registration.py | 8 +++---- lms/djangoapps/courseware/features/signup.py | 2 +- .../courseware/features/smart-accordion.py | 10 ++++---- .../courseware/features/xqueue_setup.py | 1 + 11 files changed, 50 insertions(+), 28 deletions(-) diff --git a/common/djangoapps/terrain/course_helpers.py b/common/djangoapps/terrain/course_helpers.py index 8c949de1ad..ebf5745f11 100644 --- a/common/djangoapps/terrain/course_helpers.py +++ b/common/djangoapps/terrain/course_helpers.py @@ -12,6 +12,7 @@ import os.path from urllib import quote_plus from lettuce.django import django_url + @world.absorb def create_user(uname): diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index 8dac372a64..e99dec44b3 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -13,6 +13,7 @@ logger = getLogger(__name__) def wait(step, seconds): world.wait(seconds) + @step('I reload the page$') def reload_the_page(step): world.browser.reload() @@ -72,10 +73,12 @@ def the_page_title_should_be(step, title): def the_page_title_should_contain(step, title): assert(title in world.browser.title) + @step('I log in$') def i_log_in(step): world.log_in('robot', 'test') + @step('I am a logged in user$') def i_am_logged_in_user(step): world.create_user('robot') @@ -101,10 +104,12 @@ def click_the_link_called(step, text): def should_have_the_url(step, url): assert_equals(world.browser.url, url) + @step(r'should see (?:the|a) link (?:called|with the text) "([^"]*)"$') def should_see_a_link_called(step, text): assert len(world.browser.find_link_by_text(text)) > 0 + @step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page') def should_see_in_the_page(step, text): assert_in(text, world.browser.html) @@ -130,4 +135,3 @@ def i_am_an_edx_user(step): @step(u'User "([^"]*)" is an edX user$') def registered_edx_user(step, uname): world.create_user(uname) - diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index d56ce3649b..1aac9cc72e 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -2,12 +2,29 @@ from lettuce import world, step import time from urllib import quote_plus from selenium.common.exceptions import WebDriverException +from lettuce.django import django_url + @world.absorb def wait(seconds): time.sleep(float(seconds)) +@world.absorb +def visit(url): + world.browser.visit(django_url(url)) + + +@world.absorb +def url_equals(url): + return world.browser.url == django_url(url) + + +@world.absorb +def is_css_present(css_selector): + return world.browser.is_element_present_by_css(css_selector, wait_time=4) + + @world.absorb def css_click(css_selector): try: @@ -20,22 +37,27 @@ def css_click(css_selector): time.sleep(1) world.browser.find_by_css(css_selector).click() + @world.absorb def css_fill(css_selector, text): world.browser.find_by_css(css_selector).first.fill(text) + @world.absorb def click_link(partial_text): world.browser.find_link_by_partial_text(partial_text).first.click() + @world.absorb def css_text(css_selector): return world.browser.find_by_css(css_selector).first.text + @world.absorb def css_visible(css_selector): return world.browser.find_by_css(css_selector).visible + @world.absorb def save_the_html(path='/tmp'): u = world.browser.url @@ -44,4 +66,3 @@ def save_the_html(path='/tmp'): f = open('%s/%s' % (path, filename), 'w') f.write(html) f.close - diff --git a/lms/djangoapps/courseware/features/courseware_common.py b/lms/djangoapps/courseware/features/courseware_common.py index 567254c334..6aa9559e65 100644 --- a/lms/djangoapps/courseware/features/courseware_common.py +++ b/lms/djangoapps/courseware/features/courseware_common.py @@ -1,11 +1,9 @@ from lettuce import world, step -from lettuce.django import django_url @step('I click on View Courseware') def i_click_on_view_courseware(step): - css = 'a.enter-course' - world.browser.find_by_css(css).first.click() + world.css_click('a.enter-course') @step('I click on the "([^"]*)" tab$') @@ -13,10 +11,10 @@ def i_click_on_the_tab(step, tab_text): world.click_link(tab_text) world.save_the_html() + @step('I visit the courseware URL$') def i_visit_the_course_info_url(step): - url = django_url('/courses/MITx/6.002x/2012_Fall/courseware') - world.browser.visit(url) + world.visit('/courses/MITx/6.002x/2012_Fall/courseware') @step(u'I do not see "([^"]*)" anywhere on the page') @@ -26,14 +24,15 @@ def i_do_not_see_text_anywhere_on_the_page(step, text): @step(u'I am on the dashboard page$') def i_am_on_the_dashboard_page(step): - assert world.browser.is_element_present_by_css('section.courses') - assert world.browser.url == django_url('/dashboard') + assert world.is_css_present('section.courses') + assert world.url_equals('/dashboard') @step('the "([^"]*)" tab is active$') def the_tab_is_active(step, tab_text): assert world.css_text('.course-tabs a.active') == tab_text + @step('the login dialog is visible$') def login_dialog_visible(step): assert world.css_visible('form#login_form.login_form') diff --git a/lms/djangoapps/courseware/features/login.py b/lms/djangoapps/courseware/features/login.py index 094db078ca..3e3c0efbc4 100644 --- a/lms/djangoapps/courseware/features/login.py +++ b/lms/djangoapps/courseware/features/login.py @@ -28,9 +28,7 @@ def i_should_see_the_login_error_message(step, msg): @step(u'click the dropdown arrow$') def click_the_dropdown(step): - css = ".dropdown" - e = world.browser.find_by_css(css) - e.click() + world.css_click('.dropdown') #### helper functions diff --git a/lms/djangoapps/courseware/features/openended.py b/lms/djangoapps/courseware/features/openended.py index 7601bfcc53..2f14b808a3 100644 --- a/lms/djangoapps/courseware/features/openended.py +++ b/lms/djangoapps/courseware/features/openended.py @@ -61,8 +61,7 @@ def see_the_grader_status(step, status): @step('I see the red X$') def see_the_red_x(step): - x_css = 'div.grader-status > span.incorrect' - assert world.browser.find_by_css(x_css) + assert world.is_css_present('div.grader-status > span.incorrect') @step(u'I see the grader score "([^"]*)"$') @@ -74,8 +73,7 @@ def see_the_grader_score(step, score): @step('I see the link for full output$') def see_full_output_link(step): - link_css = 'a.full' - assert world.browser.find_by_css(link_css) + assert world.is_css_present('a.full') @step('I see the spelling grading message "([^"]*)"$') diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py index d2d379a212..bdd9062ef3 100644 --- a/lms/djangoapps/courseware/features/problems.py +++ b/lms/djangoapps/courseware/features/problems.py @@ -339,7 +339,7 @@ def assert_answer_mark(step, problem_type, correctness): # At least one of the correct selectors should be present for sel in selector_dict[problem_type]: - has_expected = world.browser.is_element_present_by_css(sel, wait_time=4) + has_expected = world.is_css_present(sel) # As soon as we find the selector, break out of the loop if has_expected: @@ -366,7 +366,7 @@ def inputfield(problem_type, choice=None, input_num=1): # If the input element doesn't exist, fail immediately - assert(world.browser.is_element_present_by_css(sel, wait_time=4)) + assert world.is_css_present(sel) # Retrieve the input element return world.browser.find_by_css(sel) diff --git a/lms/djangoapps/courseware/features/registration.py b/lms/djangoapps/courseware/features/registration.py index 94b9b50f6c..63f044b16f 100644 --- a/lms/djangoapps/courseware/features/registration.py +++ b/lms/djangoapps/courseware/features/registration.py @@ -13,17 +13,17 @@ def i_register_for_the_course(step, course): register_link = intro_section.find_by_css('a.register') register_link.click() - assert world.browser.is_element_present_by_css('section.container.dashboard') + assert world.is_css_present('section.container.dashboard') @step(u'I should see the course numbered "([^"]*)" in my dashboard$') def i_should_see_that_course_in_my_dashboard(step, course): course_link_css = 'section.my-courses a[href*="%s"]' % course - assert world.browser.is_element_present_by_css(course_link_css) + assert world.is_css_present(course_link_css) @step(u'I press the "([^"]*)" button in the Unenroll dialog') def i_press_the_button_in_the_unenroll_dialog(step, value): button_css = 'section#unenroll-modal input[value="%s"]' % value - world.browser.find_by_css(button_css).click() - assert world.browser.is_element_present_by_css('section.container.dashboard') + world.css_click(button_css) + assert world.is_css_present('section.container.dashboard') diff --git a/lms/djangoapps/courseware/features/signup.py b/lms/djangoapps/courseware/features/signup.py index 3a697a6102..d9edcb215b 100644 --- a/lms/djangoapps/courseware/features/signup.py +++ b/lms/djangoapps/courseware/features/signup.py @@ -22,4 +22,4 @@ def i_check_checkbox(step, checkbox): @step('I should see "([^"]*)" in the dashboard banner$') def i_should_see_text_in_the_dashboard_banner_section(step, text): css_selector = "section.dashboard-banner h2" - assert (text in world.browser.find_by_css(css_selector).text) + assert (text in world.css_text(css_selector)) diff --git a/lms/djangoapps/courseware/features/smart-accordion.py b/lms/djangoapps/courseware/features/smart-accordion.py index 539bce96ce..8240a13905 100644 --- a/lms/djangoapps/courseware/features/smart-accordion.py +++ b/lms/djangoapps/courseware/features/smart-accordion.py @@ -32,20 +32,20 @@ def i_verify_all_the_content_of_each_course(step): pass for test_course in registered_courses: - test_course.find_by_css('a').click() + test_course.css_click('a') check_for_errors() # Get the course. E.g. 'MITx/6.002x/2012_Fall' current_course = sub('/info', '', sub('.*/courses/', '', world.browser.url)) validate_course(current_course, ids) - world.browser.find_link_by_text('Courseware').click() - assert world.browser.is_element_present_by_id('accordion', wait_time=2) + world.click_link('Courseware') + assert world.is_css_present('accordion') check_for_errors() browse_course(current_course) # clicking the user link gets you back to the user's home page - world.browser.find_by_css('.user-link').click() + world.css_click('.user-link') check_for_errors() @@ -94,7 +94,7 @@ def browse_course(course_id): world.browser.find_by_css('#accordion > nav > div')[chapter_it].find_by_tag('li')[section_it].find_by_tag('a').click() ## sometimes the course-content takes a long time to load - assert world.browser.is_element_present_by_css('.course-content', wait_time=5) + assert world.is_css_present('.course-content') ## look for server error div check_for_errors() diff --git a/lms/djangoapps/courseware/features/xqueue_setup.py b/lms/djangoapps/courseware/features/xqueue_setup.py index 23706941a9..d6d7a13a5c 100644 --- a/lms/djangoapps/courseware/features/xqueue_setup.py +++ b/lms/djangoapps/courseware/features/xqueue_setup.py @@ -3,6 +3,7 @@ from lettuce import before, after, world from django.conf import settings import threading + @before.all def setup_mock_xqueue_server(): From dde0d1676b8176119d5f33bf234221836c781aac Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 13:02:40 -0400 Subject: [PATCH 077/483] Refactored terrain/steps.py to use ui_helpers Added a wait time before checking the page HTML, and changed it to check just in the HTML body --- common/djangoapps/terrain/steps.py | 27 +++++++++++-------------- common/djangoapps/terrain/ui_helpers.py | 7 ++++++- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index e99dec44b3..dc8d2f8b87 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -26,42 +26,40 @@ def browser_back(step): @step('I (?:visit|access|open) the homepage$') def i_visit_the_homepage(step): - world.browser.visit(django_url('/')) - assert world.browser.is_element_present_by_css('header.global', 10) - + world.visit('/') + assert world.is_css_present('header.global') @step(u'I (?:visit|access|open) the dashboard$') def i_visit_the_dashboard(step): - world.browser.visit(django_url('/dashboard')) - assert world.browser.is_element_present_by_css('section.container.dashboard', 5) - + world.visit('/dashboard') + assert world.is_css_present('section.container.dashboard') @step('I should be on the dashboard page$') def i_should_be_on_the_dashboard(step): - assert world.browser.is_element_present_by_css('section.container.dashboard', 5) + assert world.is_css_present('section.container.dashboard') assert world.browser.title == 'Dashboard' @step(u'I (?:visit|access|open) the courses page$') def i_am_on_the_courses_page(step): - world.browser.visit(django_url('/courses')) - assert world.browser.is_element_present_by_css('section.courses') + world.visit('/courses') + assert world.is_css_present('section.courses') @step(u'I press the "([^"]*)" button$') def and_i_press_the_button(step, value): button_css = 'input[value="%s"]' % value - world.browser.find_by_css(button_css).first.click() + world.css_click(button_css) @step(u'I click the link with the text "([^"]*)"$') def click_the_link_with_the_text_group1(step, linktext): - world.browser.find_link_by_text(linktext).first.click() + world.click_link(linktext) @step('I should see that the path is "([^"]*)"$') def i_should_see_that_the_path_is(step, path): - assert world.browser.url == django_url(path) + assert world.url_equals(path) @step(u'the page title should be "([^"]*)"$') @@ -97,8 +95,7 @@ def i_am_staff_for_course_by_id(step, course_id): @step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$') def click_the_link_called(step, text): - world.browser.find_link_by_text(text).click() - + world.click_link(text) @step(r'should see that the url is "([^"]*)"$') def should_have_the_url(step, url): @@ -112,7 +109,7 @@ def should_see_a_link_called(step, text): @step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page') def should_see_in_the_page(step, text): - assert_in(text, world.browser.html) + assert_in(text, world.css_text('body')) @step('I am logged in$') diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 1aac9cc72e..3009d1fa8d 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -50,7 +50,12 @@ def click_link(partial_text): @world.absorb def css_text(css_selector): - return world.browser.find_by_css(css_selector).first.text + + # Wait for the css selector to appear + if world.is_css_present(css_selector): + return world.browser.find_by_css(css_selector).first.text + else: + return "" @world.absorb From e69931ec5a06ecec9bc57b2875181e94b9b2f059 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 13:45:25 -0400 Subject: [PATCH 078/483] Refactored studio lettuce tests to use terrain/ui_helpers for ui manipulation --- .../features/advanced-settings.py | 40 ++----- .../contentstore/features/common.py | 102 +++++------------- .../contentstore/features/courses.py | 15 ++- .../contentstore/features/section.py | 26 ++--- .../contentstore/features/signup.py | 2 +- .../features/studio-overview-togglesection.py | 24 ++--- .../contentstore/features/subsection.py | 15 ++- common/djangoapps/terrain/ui_helpers.py | 34 ++++++ 8 files changed, 109 insertions(+), 149 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 7e86e94a31..0232c3b908 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -2,8 +2,6 @@ from lettuce import world, step from common import * import time from terrain.steps import reload_the_page -from selenium.common.exceptions import WebDriverException -from selenium.webdriver.support import expected_conditions as EC from nose.tools import assert_true, assert_false, assert_equal @@ -22,9 +20,9 @@ DISPLAY_NAME_VALUE = '"Robot Super Course"' def i_select_advanced_settings(step): expand_icon_css = 'li.nav-course-settings i.icon-expand' if world.browser.is_element_present_by_css(expand_icon_css): - css_click(expand_icon_css) + world.css_click(expand_icon_css) link_css = 'li.nav-course-settings-advanced a' - css_click(link_css) + world.css_click(link_css) @step('I am on the Advanced Course Settings page in Studio$') @@ -35,24 +33,8 @@ def i_am_on_advanced_course_settings(step): @step(u'I press the "([^"]*)" notification button$') def press_the_notification_button(step, name): - def is_visible(driver): - return EC.visibility_of_element_located((By.CSS_SELECTOR, css,)) - - # def is_invisible(driver): - # return EC.invisibility_of_element_located((By.CSS_SELECTOR,css,)) - css = 'a.%s-button' % name.lower() - wait_for(is_visible) - time.sleep(float(1)) - css_click_at(css) - -# is_invisible is not returning a boolean, not working -# try: -# css_click_at(css) -# wait_for(is_invisible) -# except WebDriverException, e: -# css_click_at(css) -# wait_for(is_invisible) + world.css_click_at(css) @step(u'I edit the value of a policy key$') @@ -61,7 +43,7 @@ def edit_the_value_of_a_policy_key(step): It is hard to figure out how to get into the CodeMirror area, so cheat and do it from the policy key field :) """ - e = css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)] + e = world.css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)] e._element.send_keys(Keys.TAB, Keys.END, Keys.ARROW_LEFT, ' ', 'X') @@ -85,7 +67,7 @@ def i_see_default_advanced_settings(step): @step('the settings are alphabetized$') def they_are_alphabetized(step): - key_elements = css_find(KEY_CSS) + key_elements = world.css_find(KEY_CSS) all_keys = [] for key in key_elements: all_keys.append(key.value) @@ -118,13 +100,13 @@ def assert_policy_entries(expected_keys, expected_values): for counter in range(len(expected_keys)): index = get_index_of(expected_keys[counter]) assert_false(index == -1, "Could not find key: " + expected_keys[counter]) - assert_equal(expected_values[counter], css_find(VALUE_CSS)[index].value, "value is incorrect") + assert_equal(expected_values[counter], world.css_find(VALUE_CSS)[index].value, "value is incorrect") def get_index_of(expected_key): - for counter in range(len(css_find(KEY_CSS))): + for counter in range(len(world.css_find(KEY_CSS))): # Sometimes get stale reference if I hold on to the array of elements - key = css_find(KEY_CSS)[counter].value + key = world.css_find(KEY_CSS)[counter].value if key == expected_key: return counter @@ -133,14 +115,14 @@ def get_index_of(expected_key): def get_display_name_value(): index = get_index_of(DISPLAY_NAME_KEY) - return css_find(VALUE_CSS)[index].value + return world.css_find(VALUE_CSS)[index].value def change_display_name_value(step, new_value): - e = css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)] + e = world.css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)] display_name = get_display_name_value() for count in range(len(display_name)): e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE) # Must delete "" before typing the JSON value e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE, Keys.BACK_SPACE, new_value) - press_the_notification_button(step, "Save") \ No newline at end of file + press_the_notification_button(step, "Save") diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 820b60123b..4cc5759949 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -1,11 +1,6 @@ from lettuce import world, step -from lettuce.django import django_url from nose.tools import assert_true from nose.tools import assert_equal -from selenium.webdriver.support.ui import WebDriverWait -from selenium.common.exceptions import WebDriverException, StaleElementReferenceException -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.by import By from xmodule.modulestore.django import _MODULESTORES, modulestore from xmodule.templates import update_templates @@ -20,9 +15,9 @@ def i_visit_the_studio_homepage(step): # To make this go to port 8001, put # LETTUCE_SERVER_PORT = 8001 # in your settings.py file. - world.browser.visit(django_url('/')) + world.visit('/') signin_css = 'a.action-signin' - assert world.browser.is_element_present_by_css(signin_css, 10) + assert world.is_css_present(signin_css) @step('I am logged into Studio$') @@ -43,7 +38,7 @@ def i_press_the_category_delete_icon(step, category): css = 'a.delete-button.delete-subsection-button span.delete-icon' else: assert False, 'Invalid category: %s' % category - css_click(css) + world.css_click(css) @step('I have opened a new course in Studio$') @@ -87,56 +82,6 @@ def flush_xmodule_store(): update_templates() -def assert_css_with_text(css, text): - assert_true(world.browser.is_element_present_by_css(css, 5)) - assert_equal(world.browser.find_by_css(css).text, text) - - -def css_click(css): - ''' - First try to use the regular click method, - but if clicking in the middle of an element - doesn't work it might be that it thinks some other - element is on top of it there so click in the upper left - ''' - try: - css_find(css).first.click() - except WebDriverException, e: - css_click_at(css) - - -def css_click_at(css, x=10, y=10): - ''' - A method to click at x,y coordinates of the element - rather than in the center of the element - ''' - e = css_find(css).first - e.action_chains.move_to_element_with_offset(e._element, x, y) - e.action_chains.click() - e.action_chains.perform() - - -def css_fill(css, value): - world.browser.find_by_css(css).first.fill(value) - - -def css_find(css): - def is_visible(driver): - return EC.visibility_of_element_located((By.CSS_SELECTOR,css,)) - - world.browser.is_element_present_by_css(css, 5) - wait_for(is_visible) - return world.browser.find_by_css(css) - - -def wait_for(func): - WebDriverWait(world.browser.driver, 5).until(func) - - -def id_find(id): - return world.browser.find_by_id(id) - - def clear_courses(): flush_xmodule_store() @@ -145,9 +90,9 @@ def fill_in_course_info( name='Robot Super Course', org='MITx', num='101'): - css_fill('.new-course-name', name) - css_fill('.new-course-org', org) - css_fill('.new-course-number', num) + world.css_fill('.new-course-name', name) + world.css_fill('.new-course-org', org) + world.css_fill('.new-course-number', num) def log_into_studio( @@ -155,21 +100,22 @@ def log_into_studio( email='robot+studio@edx.org', password='test', is_staff=False): - create_studio_user(uname=uname, email=email, is_staff=is_staff) - world.browser.cookies.delete() - world.browser.visit(django_url('/')) - signin_css = 'a.action-signin' - world.browser.is_element_present_by_css(signin_css, 10) - # click the signin button - css_click(signin_css) + create_studio_user(uname=uname, email=email, is_staff=is_staff) + + world.browser.cookies.delete() + world.visit('/') + + signin_css = 'a.action-signin' + world.is_css_present(signin_css) + world.css_click(signin_css) login_form = world.browser.find_by_css('form#login_form') login_form.find_by_name('email').fill(email) login_form.find_by_name('password').fill(password) login_form.find_by_name('submit').click() - assert_true(world.browser.is_element_present_by_css('.new-course-button', 5)) + assert_true(world.is_css_present('.new-course-button')) def create_a_course(): @@ -184,26 +130,26 @@ def create_a_course(): world.browser.reload() course_link_css = 'span.class-name' - css_click(course_link_css) + world.css_click(course_link_css) course_title_css = 'span.course-title' - assert_true(world.browser.is_element_present_by_css(course_title_css, 5)) + assert_true(world.is_css_present(course_title_css)) def add_section(name='My Section'): link_css = 'a.new-courseware-section-button' - css_click(link_css) + world.css_click(link_css) name_css = 'input.new-section-name' save_css = 'input.new-section-name-save' - css_fill(name_css, name) - css_click(save_css) + world.css_fill(name_css, name) + world.css_click(save_css) span_css = 'span.section-name-span' - assert_true(world.browser.is_element_present_by_css(span_css, 5)) + assert_true(world.is_css_present(span_css)) def add_subsection(name='Subsection One'): css = 'a.new-subsection-item' - css_click(css) + world.css_click(css) name_css = 'input.new-subsection-name-input' save_css = 'input.new-subsection-name-save' - css_fill(name_css, name) - css_click(save_css) + world.css_fill(name_css, name) + world.css_click(save_css) diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index e394165f08..8301e6708f 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -11,7 +11,7 @@ def no_courses(step): @step('I click the New Course button$') def i_click_new_course(step): - css_click('.new-course-button') + world.css_click('.new-course-button') @step('I fill in the new course information$') @@ -27,7 +27,7 @@ def i_create_a_course(step): @step('I click the course link in My Courses$') def i_click_the_course_link_in_my_courses(step): course_css = 'span.class-name' - css_click(course_css) + world.css_click(course_css) ############ ASSERTIONS ################### @@ -35,28 +35,27 @@ def i_click_the_course_link_in_my_courses(step): @step('the Courseware page has loaded in Studio$') def courseware_page_has_loaded_in_studio(step): course_title_css = 'span.course-title' - assert world.browser.is_element_present_by_css(course_title_css) + assert world.is_css_present(course_title_css) @step('I see the course listed in My Courses$') def i_see_the_course_in_my_courses(step): course_css = 'span.class-name' - assert_css_with_text(course_css, 'Robot Super Course') - + assert world.css_has_text(course_css, 'Robot Super Course') @step('the course is loaded$') def course_is_loaded(step): class_css = 'a.class-name' - assert_css_with_text(class_css, 'Robot Super Course') + assert world.css_has_text(course_css, 'Robot Super Cousre') @step('I am on the "([^"]*)" tab$') def i_am_on_tab(step, tab_name): header_css = 'div.inner-wrapper h1' - assert_css_with_text(header_css, tab_name) + assert world.css_has_text(header_css, tab_name) @step('I see a link for adding a new section$') def i_see_new_section_link(step): link_css = 'a.new-courseware-section-button' - assert_css_with_text(link_css, '+ New Section') + assert world.css_has_text(link_css, '+ New Section') diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py index b5ddb48a09..e57d50bbfe 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -10,7 +10,7 @@ import time @step('I click the new section link$') def i_click_new_section_link(step): link_css = 'a.new-courseware-section-button' - css_click(link_css) + world.css_click(link_css) @step('I enter the section name and click save$') @@ -31,19 +31,19 @@ def i_have_added_new_section(step): @step('I click the Edit link for the release date$') def i_click_the_edit_link_for_the_release_date(step): button_css = 'div.section-published-date a.edit-button' - css_click(button_css) + world.css_click(button_css) @step('I save a new section release date$') def i_save_a_new_section_release_date(step): date_css = 'input.start-date.date.hasDatepicker' time_css = 'input.start-time.time.ui-timepicker-input' - css_fill(date_css, '12/25/2013') + world.css_fill(date_css, '12/25/2013') # hit TAB to get to the time field - e = css_find(date_css).first + e = world.css_find(date_css).first e._element.send_keys(Keys.TAB) - css_fill(time_css, '12:00am') - e = css_find(time_css).first + world.css_fill(time_css, '12:00am') + e = world.css_find(time_css).first e._element.send_keys(Keys.TAB) time.sleep(float(1)) world.browser.click_link_by_text('Save') @@ -64,13 +64,13 @@ def i_see_my_section_name_with_quote_on_the_courseware_page(step): @step('I click to edit the section name$') def i_click_to_edit_section_name(step): - css_click('span.section-name-span') + world.css_click('span.section-name-span') @step('I see the complete section name with a quote in the editor$') def i_see_complete_section_name_with_quote_in_editor(step): css = '.edit-section-name' - assert world.browser.is_element_present_by_css(css, 5) + assert world.is_css_present(css) assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"') @@ -85,7 +85,7 @@ def i_see_a_release_date_for_my_section(step): import re css = 'span.published-status' - assert world.browser.is_element_present_by_css(css) + assert world.is_css_present(css) status_text = world.browser.find_by_css(css).text # e.g. 11/06/2012 at 16:25 @@ -99,7 +99,7 @@ def i_see_a_release_date_for_my_section(step): @step('I see a link to create a new subsection$') def i_see_a_link_to_create_a_new_subsection(step): css = 'a.new-subsection-item' - assert world.browser.is_element_present_by_css(css) + assert world.is_css_present(css) @step('the section release date picker is not visible$') @@ -120,10 +120,10 @@ def the_section_release_date_is_updated(step): def save_section_name(name): name_css = '.new-section-name' save_css = '.new-section-name-save' - css_fill(name_css, name) - css_click(save_css) + world.css_fill(name_css, name) + world.css_click(save_css) def see_my_section_on_the_courseware_page(name): section_css = 'span.section-name-span' - assert_css_with_text(section_css, name) + assert world.css_has_text(section_css, name) diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py index e8d0dd8229..cd4adb79fb 100644 --- a/cms/djangoapps/contentstore/features/signup.py +++ b/cms/djangoapps/contentstore/features/signup.py @@ -17,7 +17,7 @@ def i_press_the_button_on_the_registration_form(step): submit_css = 'form#register_form button#submit' # Workaround for click not working on ubuntu # for some unknown reason. - e = css_find(submit_css) + e = world.css_find(submit_css) e.type(' ') @step('I should see be on the studio home page$') diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py index 060d592cfd..85a25a55ac 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py @@ -49,7 +49,7 @@ def have_a_course_with_two_sections(step): def navigate_to_the_course_overview_page(step): log_into_studio(is_staff=True) course_locator = '.class-name' - css_click(course_locator) + world.css_click(course_locator) @step(u'I navigate to the courseware page of a course with multiple sections') @@ -66,44 +66,44 @@ def i_add_a_section(step): @step(u'I click the "([^"]*)" link$') def i_click_the_text_span(step, text): span_locator = '.toggle-button-sections span' - assert_true(world.browser.is_element_present_by_css(span_locator, 5)) + assert_true(world.browser.is_element_present_by_css(span_locator)) # first make sure that the expand/collapse text is the one you expected assert_equal(world.browser.find_by_css(span_locator).value, text) - css_click(span_locator) + world.css_click(span_locator) @step(u'I collapse the first section$') def i_collapse_a_section(step): collapse_locator = 'section.courseware-section a.collapse' - css_click(collapse_locator) + world.css_click(collapse_locator) @step(u'I expand the first section$') def i_expand_a_section(step): expand_locator = 'section.courseware-section a.expand' - css_click(expand_locator) + world.css_click(expand_locator) @step(u'I see the "([^"]*)" link$') def i_see_the_span_with_text(step, text): span_locator = '.toggle-button-sections span' - assert_true(world.browser.is_element_present_by_css(span_locator, 5)) - assert_equal(world.browser.find_by_css(span_locator).value, text) - assert_true(world.browser.find_by_css(span_locator).visible) + assert_true(world.is_css_present(span_locator)) + assert_equal(world.css_find(span_locator).value, text) + assert_true(world.css_visible(span_locator)) @step(u'I do not see the "([^"]*)" link$') def i_do_not_see_the_span_with_text(step, text): # Note that the span will exist on the page but not be visible span_locator = '.toggle-button-sections span' - assert_true(world.browser.is_element_present_by_css(span_locator)) - assert_false(world.browser.find_by_css(span_locator).visible) + assert_true(world.is_css_present(span_locator)) + assert_false(world.css_visible(span_locator)) @step(u'all sections are expanded$') def all_sections_are_expanded(step): subsection_locator = 'div.subsection-list' - subsections = world.browser.find_by_css(subsection_locator) + subsections = world.css_find(subsection_locator) for s in subsections: assert_true(s.visible) @@ -111,6 +111,6 @@ def all_sections_are_expanded(step): @step(u'all sections are collapsed$') def all_sections_are_expanded(step): subsection_locator = 'div.subsection-list' - subsections = world.browser.find_by_css(subsection_locator) + subsections = world.css_find(subsection_locator) for s in subsections: assert_false(s.visible) diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index 88e1424898..f5863be27b 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -15,8 +15,7 @@ def i_have_opened_a_new_course_section(step): @step('I click the New Subsection link') def i_click_the_new_subsection_link(step): - css = 'a.new-subsection-item' - css_click(css) + world.css_click('a.new-subsection-item') @step('I enter the subsection name and click save$') @@ -31,13 +30,13 @@ def i_save_subsection_name_with_quote(step): @step('I click to edit the subsection name$') def i_click_to_edit_subsection_name(step): - css_click('span.subsection-name-value') + world.css_click('span.subsection-name-value') @step('I see the complete subsection name with a quote in the editor$') def i_see_complete_subsection_name_with_quote_in_editor(step): css = '.subsection-display-name-input' - assert world.browser.is_element_present_by_css(css, 5) + assert world.is_css_present(css) assert_equal(world.browser.find_by_css(css).value, 'Subsection With "Quote"') @@ -70,11 +69,11 @@ def the_subsection_does_not_exist(step): def save_subsection_name(name): name_css = 'input.new-subsection-name-input' save_css = 'input.new-subsection-name-save' - css_fill(name_css, name) - css_click(save_css) + world.css_fill(name_css, name) + world.css_click(save_css) def see_subsection_name(name): css = 'span.subsection-name' - assert world.browser.is_element_present_by_css(css) + assert world.is_css_present(css) css = 'span.subsection-name-value' - assert_css_with_text(css, name) + assert world.css_has_text(css, name) diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 3009d1fa8d..e2f701d089 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -2,6 +2,9 @@ from lettuce import world, step import time from urllib import quote_plus from selenium.common.exceptions import WebDriverException +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait from lettuce.django import django_url @@ -9,6 +12,9 @@ from lettuce.django import django_url def wait(seconds): time.sleep(float(seconds)) +@world.absorb +def wait_for(func): + WebDriverWait(world.browser.driver, 5).until(func) @world.absorb def visit(url): @@ -24,9 +30,27 @@ def url_equals(url): def is_css_present(css_selector): return world.browser.is_element_present_by_css(css_selector, wait_time=4) +@world.absorb +def css_has_text(css_selector, text): + return world.css_text(css_selector) == text + +@world.absorb +def css_find(css): + def is_visible(driver): + return EC.visibility_of_element_located((By.CSS_SELECTOR,css,)) + + world.browser.is_element_present_by_css(css, 5) + wait_for(is_visible) + return world.browser.find_by_css(css) @world.absorb def css_click(css_selector): + ''' + First try to use the regular click method, + but if clicking in the middle of an element + doesn't work it might be that it thinks some other + element is on top of it there so click in the upper left + ''' try: world.browser.find_by_css(css_selector).click() @@ -37,6 +61,16 @@ def css_click(css_selector): time.sleep(1) world.browser.find_by_css(css_selector).click() +@world.absorb +def css_click_at(css, x=10, y=10): + ''' + A method to click at x,y coordinates of the element + rather than in the center of the element + ''' + e = css_find(css).first + e.action_chains.move_to_element_with_offset(e._element, x, y) + e.action_chains.click() + e.action_chains.perform() @world.absorb def css_fill(css_selector, text): From a58ae9b62d60450b7bf18a49531487e2150cf094 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 13:49:50 -0400 Subject: [PATCH 079/483] Refactored studio lettuce test section.py to use more of ui helpers --- cms/djangoapps/contentstore/features/section.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py index e57d50bbfe..41236f6dfd 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -105,13 +105,13 @@ def i_see_a_link_to_create_a_new_subsection(step): @step('the section release date picker is not visible$') def the_section_release_date_picker_not_visible(step): css = 'div.edit-subsection-publish-settings' - assert False, world.browser.find_by_css(css).visible + assert not world.css_visible(css) @step('the section release date is updated$') def the_section_release_date_is_updated(step): css = 'span.published-status' - status_text = world.browser.find_by_css(css).text + status_text = world.css_text(css) assert_equal(status_text,'Will Release: 12/25/2013 at 12:00am') From 00d25b684cf10bd2c8dd39a5077e365b3259bfde Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 14:04:04 -0400 Subject: [PATCH 080/483] Moved modulestore flush code into terrain/course_helpers --- .../contentstore/features/common.py | 19 +------------------ .../contentstore/features/courses.py | 2 +- .../features/studio-overview-togglesection.py | 6 +++--- .../contentstore/features/subsection.py | 2 +- common/djangoapps/terrain/course_helpers.py | 15 +++++++++++++++ lms/djangoapps/courseware/features/common.py | 15 +-------------- 6 files changed, 22 insertions(+), 37 deletions(-) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 4cc5759949..0b5c9acbed 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -43,7 +43,7 @@ def i_press_the_category_delete_icon(step, category): @step('I have opened a new course in Studio$') def i_have_opened_a_new_course(step): - clear_courses() + world.clear_courses() log_into_studio() create_a_course() @@ -69,23 +69,6 @@ def create_studio_user( user_profile = world.UserProfileFactory(user=studio_user) -def flush_xmodule_store(): - # Flush and initialize the module store - # It needs the templates because it creates new records - # by cloning from the template. - # Note that if your test module gets in some weird state - # (though it shouldn't), do this manually - # from the bash shell to drop it: - # $ mongo test_xmodule --eval "db.dropDatabase()" - _MODULESTORES = {} - modulestore().collection.drop() - update_templates() - - -def clear_courses(): - flush_xmodule_store() - - def fill_in_course_info( name='Robot Super Course', org='MITx', diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index 8301e6708f..348cc25e97 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -6,7 +6,7 @@ from common import * @step('There are no courses$') def no_courses(step): - clear_courses() + world.clear_courses() @step('I click the New Course button$') diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py index 85a25a55ac..dc22d3ad1a 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py @@ -8,13 +8,13 @@ logger = getLogger(__name__) @step(u'I have a course with no sections$') def have_a_course(step): - clear_courses() + world.clear_courses() course = world.CourseFactory.create() @step(u'I have a course with 1 section$') def have_a_course_with_1_section(step): - clear_courses() + world.clear_courses() course = world.CourseFactory.create() section = world.ItemFactory.create(parent_location=course.location) subsection1 = world.ItemFactory.create( @@ -25,7 +25,7 @@ def have_a_course_with_1_section(step): @step(u'I have a course with multiple sections$') def have_a_course_with_two_sections(step): - clear_courses() + world.clear_courses() course = world.CourseFactory.create() section = world.ItemFactory.create(parent_location=course.location) subsection1 = world.ItemFactory.create( diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index f5863be27b..2094e65ccb 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -7,7 +7,7 @@ from nose.tools import assert_equal @step('I have opened a new course section in Studio$') def i_have_opened_a_new_course_section(step): - clear_courses() + world.clear_courses() log_into_studio() create_a_course() add_section() diff --git a/common/djangoapps/terrain/course_helpers.py b/common/djangoapps/terrain/course_helpers.py index ebf5745f11..2ac3befd82 100644 --- a/common/djangoapps/terrain/course_helpers.py +++ b/common/djangoapps/terrain/course_helpers.py @@ -7,6 +7,8 @@ from django.contrib.auth import authenticate, login from django.contrib.auth.middleware import AuthenticationMiddleware from django.contrib.sessions.middleware import SessionMiddleware from student.models import CourseEnrollment +from xmodule.modulestore.django import _MODULESTORES, modulestore +from xmodule.templates import update_templates from bs4 import BeautifulSoup import os.path from urllib import quote_plus @@ -119,3 +121,16 @@ def save_the_course_content(path='/tmp'): f = open('%s/%s' % (path, filename), 'w') f.write(output) f.close + +@world.absorb +def clear_courses(): + # Flush and initialize the module store + # It needs the templates because it creates new records + # by cloning from the template. + # Note that if your test module gets in some weird state + # (though it shouldn't), do this manually + # from the bash shell to drop it: + # $ mongo test_xmodule --eval "db.dropDatabase()" + _MODULESTORES = {} + modulestore().collection.drop() + update_templates() diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index 2d366d462d..f015725ae9 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -24,7 +24,7 @@ def create_course(step, course): # First clear the modulestore so we don't try to recreate # the same course twice # This also ensures that the necessary templates are loaded - flush_xmodule_store() + world.clear_courses() # Create the course # We always use the same org and display name, @@ -65,19 +65,6 @@ def add_tab_to_course(step, course, extra_tab_name): display_name=str(extra_tab_name)) -def flush_xmodule_store(): - # Flush and initialize the module store - # It needs the templates because it creates new records - # by cloning from the template. - # Note that if your test module gets in some weird state - # (though it shouldn't), do this manually - # from the bash shell to drop it: - # $ mongo test_xmodule --eval "db.dropDatabase()" - _MODULESTORES = {} - modulestore().collection.drop() - update_templates() - - def course_id(course_num): return "%s/%s/%s" % (TEST_COURSE_ORG, course_num, TEST_COURSE_NAME.replace(" ", "_")) From 27d5ebf027224239c5109820794d6e5c0098930d Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 22 Mar 2013 14:27:10 -0400 Subject: [PATCH 081/483] pep8 fixes --- .../features/advanced-settings.feature | 8 +++--- .../features/advanced-settings.py | 1 + .../contentstore/features/common.py | 1 + .../contentstore/features/courses.feature | 2 +- .../contentstore/features/courses.py | 1 + .../contentstore/features/section.py | 2 +- .../contentstore/features/signup.py | 1 + .../studio-overview-togglesection.feature | 28 +++++++++---------- .../contentstore/features/subsection.py | 1 + common/djangoapps/terrain/course_helpers.py | 1 + common/djangoapps/terrain/steps.py | 3 ++ common/djangoapps/terrain/ui_helpers.py | 11 ++++++-- .../features/high-level-tabs.feature | 2 +- 13 files changed, 39 insertions(+), 23 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.feature b/cms/djangoapps/contentstore/features/advanced-settings.feature index af97709ad0..66039e19b1 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.feature +++ b/cms/djangoapps/contentstore/features/advanced-settings.feature @@ -1,6 +1,6 @@ Feature: Advanced (manual) course policy In order to specify course policy settings for which no custom user interface exists - I want to be able to manually enter JSON key/value pairs + I want to be able to manually enter JSON key /value pairs Scenario: A course author sees default advanced settings Given I have opened a new course in Studio @@ -27,16 +27,16 @@ Feature: Advanced (manual) course policy And I reload the page Then the policy key value is changed - Scenario: Test how multi-line input appears + Scenario: Test how multi -line input appears Given I am on the Advanced Course Settings page in Studio When I create a JSON object as a value Then it is displayed as formatted And I reload the page Then it is displayed as formatted - Scenario: Test automatic quoting of non-JSON values + Scenario: Test automatic quoting of non -JSON values Given I am on the Advanced Course Settings page in Studio - When I create a non-JSON value not in quotes + When I create a non -JSON value not in quotes Then it is displayed as a string And I reload the page Then it is displayed as a string diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 0232c3b908..a2708d8c96 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -16,6 +16,7 @@ DISPLAY_NAME_KEY = "display_name" DISPLAY_NAME_VALUE = '"Robot Super Course"' ############### ACTIONS #################### + @step('I select the Advanced Settings$') def i_select_advanced_settings(step): expand_icon_css = 'li.nav-course-settings i.icon-expand' diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 0b5c9acbed..870ab89694 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -10,6 +10,7 @@ from logging import getLogger logger = getLogger(__name__) ########### STEP HELPERS ############## + @step('I (?:visit|access|open) the Studio homepage$') def i_visit_the_studio_homepage(step): # To make this go to port 8001, put diff --git a/cms/djangoapps/contentstore/features/courses.feature b/cms/djangoapps/contentstore/features/courses.feature index 39d39b50aa..455313b0e2 100644 --- a/cms/djangoapps/contentstore/features/courses.feature +++ b/cms/djangoapps/contentstore/features/courses.feature @@ -10,4 +10,4 @@ Feature: Create Course And I fill in the new course information And I press the "Save" button Then the Courseware page has loaded in Studio - And I see a link for adding a new section \ No newline at end of file + And I see a link for adding a new section diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index 348cc25e97..b3b6f91bdb 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -43,6 +43,7 @@ def i_see_the_course_in_my_courses(step): course_css = 'span.class-name' assert world.css_has_text(course_css, 'Robot Super Course') + @step('the course is loaded$') def course_is_loaded(step): class_css = 'a.class-name' diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py index 41236f6dfd..65f3bd4897 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -112,7 +112,7 @@ def the_section_release_date_picker_not_visible(step): def the_section_release_date_is_updated(step): css = 'span.published-status' status_text = world.css_text(css) - assert_equal(status_text,'Will Release: 12/25/2013 at 12:00am') + assert_equal(status_text, 'Will Release: 12/25/2013 at 12:00am') ############ HELPER METHODS ################### diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py index cd4adb79fb..2dcf0d63fe 100644 --- a/cms/djangoapps/contentstore/features/signup.py +++ b/cms/djangoapps/contentstore/features/signup.py @@ -20,6 +20,7 @@ def i_press_the_button_on_the_registration_form(step): e = world.css_find(submit_css) e.type(' ') + @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') diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature index 52c10e41a8..88492d55e3 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature @@ -1,30 +1,30 @@ Feature: Overview Toggle Section In order to quickly view the details of a course's section or to scan the inventory of sections - As a course author - I want to toggle the visibility of each section's subsection details in the overview listing + As a course author + I want to toggle the visibility of each section's subsection details in the overview listing Scenario: The default layout for the overview page is to show sections in expanded view Given I have a course with multiple sections - When I navigate to the course overview page - Then I see the "Collapse All Sections" link - And all sections are expanded + When I navigate to the course overview page + Then I see the "Collapse All Sections" link + And all sections are expanded - Scenario: Expand/collapse for a course with no sections + Scenario: Expand /collapse for a course with no sections Given I have a course with no sections - When I navigate to the course overview page - Then I do not see the "Collapse All Sections" link + When I navigate to the course overview page + Then I do not see the "Collapse All Sections" link Scenario: Collapse link appears after creating first section of a course Given I have a course with no sections - When I navigate to the course overview page - And I add a section - Then I see the "Collapse All Sections" link - And all sections are expanded + When I navigate to the course overview page + And I add a section + Then I see the "Collapse All Sections" link + And all sections are expanded - @skip-phantom + @skip -phantom Scenario: Collapse link is not removed after last section of a course is deleted Given I have a course with 1 section - And I navigate to the course overview page + And I navigate to the course overview page When I press the "section" delete icon And I confirm the alert Then I see the "Collapse All Sections" link diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index 2094e65ccb..8695ea1c4f 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -72,6 +72,7 @@ def save_subsection_name(name): world.css_fill(name_css, name) world.css_click(save_css) + def see_subsection_name(name): css = 'span.subsection-name' assert world.is_css_present(css) diff --git a/common/djangoapps/terrain/course_helpers.py b/common/djangoapps/terrain/course_helpers.py index 2ac3befd82..85dfa85b37 100644 --- a/common/djangoapps/terrain/course_helpers.py +++ b/common/djangoapps/terrain/course_helpers.py @@ -122,6 +122,7 @@ def save_the_course_content(path='/tmp'): f.write(output) f.close + @world.absorb def clear_courses(): # Flush and initialize the module store diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index dc8d2f8b87..bf78a1d2b7 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -29,11 +29,13 @@ def i_visit_the_homepage(step): world.visit('/') assert world.is_css_present('header.global') + @step(u'I (?:visit|access|open) the dashboard$') def i_visit_the_dashboard(step): world.visit('/dashboard') assert world.is_css_present('section.container.dashboard') + @step('I should be on the dashboard page$') def i_should_be_on_the_dashboard(step): assert world.is_css_present('section.container.dashboard') @@ -97,6 +99,7 @@ def i_am_staff_for_course_by_id(step, course_id): def click_the_link_called(step, text): world.click_link(text) + @step(r'should see that the url is "([^"]*)"$') def should_have_the_url(step, url): assert_equals(world.browser.url, url) diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index e2f701d089..6dadb976a7 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -12,10 +12,12 @@ from lettuce.django import django_url def wait(seconds): time.sleep(float(seconds)) + @world.absorb def wait_for(func): WebDriverWait(world.browser.driver, 5).until(func) + @world.absorb def visit(url): world.browser.visit(django_url(url)) @@ -30,23 +32,26 @@ def url_equals(url): def is_css_present(css_selector): return world.browser.is_element_present_by_css(css_selector, wait_time=4) + @world.absorb def css_has_text(css_selector, text): return world.css_text(css_selector) == text + @world.absorb def css_find(css): def is_visible(driver): - return EC.visibility_of_element_located((By.CSS_SELECTOR,css,)) + return EC.visibility_of_element_located((By.CSS_SELECTOR, css,)) world.browser.is_element_present_by_css(css, 5) wait_for(is_visible) return world.browser.find_by_css(css) + @world.absorb def css_click(css_selector): ''' - First try to use the regular click method, + First try to use the regular click method, but if clicking in the middle of an element doesn't work it might be that it thinks some other element is on top of it there so click in the upper left @@ -61,6 +66,7 @@ def css_click(css_selector): time.sleep(1) world.browser.find_by_css(css_selector).click() + @world.absorb def css_click_at(css, x=10, y=10): ''' @@ -72,6 +78,7 @@ def css_click_at(css, x=10, y=10): e.action_chains.click() e.action_chains.perform() + @world.absorb def css_fill(css_selector, text): world.browser.find_by_css(css_selector).first.fill(text) diff --git a/lms/djangoapps/courseware/features/high-level-tabs.feature b/lms/djangoapps/courseware/features/high-level-tabs.feature index 473f3f1572..c60ec7b374 100644 --- a/lms/djangoapps/courseware/features/high-level-tabs.feature +++ b/lms/djangoapps/courseware/features/high-level-tabs.feature @@ -3,7 +3,7 @@ Feature: All the high level tabs should work As a student I want to navigate through the high level tabs -Scenario: I can navigate to all high -level tabs in a course +Scenario: I can navigate to all high - level tabs in a course Given: I am registered for the course "6.002x" And The course "6.002x" has extra tab "Custom Tab" And I am logged in From 0500ba4dd5e4a8563a31c6557f8ca331cdba8cfa Mon Sep 17 00:00:00 2001 From: Will Daly Date: Tue, 26 Mar 2013 11:17:56 -0400 Subject: [PATCH 082/483] Disabled pylint warnings for lettuce steps: * Missing docstring * Redefining name from outer scope --- cms/djangoapps/contentstore/features/advanced-settings.py | 3 +++ cms/djangoapps/contentstore/features/common.py | 3 +++ cms/djangoapps/contentstore/features/courses.py | 3 +++ cms/djangoapps/contentstore/features/section.py | 3 +++ cms/djangoapps/contentstore/features/signup.py | 3 +++ .../contentstore/features/studio-overview-togglesection.py | 3 +++ cms/djangoapps/contentstore/features/subsection.py | 3 +++ common/djangoapps/terrain/course_helpers.py | 3 +++ common/djangoapps/terrain/steps.py | 3 +++ common/djangoapps/terrain/ui_helpers.py | 3 +++ lms/djangoapps/courseware/features/common.py | 3 +++ lms/djangoapps/courseware/features/courseware.py | 3 +++ lms/djangoapps/courseware/features/courseware_common.py | 3 +++ lms/djangoapps/courseware/features/homepage.py | 3 +++ lms/djangoapps/courseware/features/login.py | 3 +++ lms/djangoapps/courseware/features/openended.py | 3 +++ lms/djangoapps/courseware/features/problems.py | 2 ++ lms/djangoapps/courseware/features/registration.py | 3 +++ lms/djangoapps/courseware/features/signup.py | 4 +++- lms/djangoapps/courseware/features/smart-accordion.py | 3 +++ lms/djangoapps/courseware/features/xqueue_setup.py | 4 +++- 21 files changed, 62 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index a2708d8c96..16562b6b15 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from common import * import time diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 870ab89694..3878340af3 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from nose.tools import assert_true from nose.tools import assert_equal diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index b3b6f91bdb..5da7720945 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from common import * diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py index 65f3bd4897..0c0f5536a0 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from common import * from nose.tools import assert_equal diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py index 2dcf0d63fe..6ca358183b 100644 --- a/cms/djangoapps/contentstore/features/signup.py +++ b/cms/djangoapps/contentstore/features/signup.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from common import * diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py index dc22d3ad1a..7f717b731c 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from common import * from nose.tools import assert_true, assert_false, assert_equal diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index 8695ea1c4f..54f49f2fa6 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from common import * from nose.tools import assert_equal diff --git a/common/djangoapps/terrain/course_helpers.py b/common/djangoapps/terrain/course_helpers.py index 85dfa85b37..f0df456c80 100644 --- a/common/djangoapps/terrain/course_helpers.py +++ b/common/djangoapps/terrain/course_helpers.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from .factories import * from django.conf import settings diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index bf78a1d2b7..a8a32db173 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from .course_helpers import * from .ui_helpers import * diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 6dadb976a7..d4d99e17b5 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step import time from urllib import quote_plus diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index f015725ae9..f6256adfa1 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from nose.tools import assert_equals, assert_in from lettuce.django import django_url diff --git a/lms/djangoapps/courseware/features/courseware.py b/lms/djangoapps/courseware/features/courseware.py index 7e99cc9f55..234f3a84d2 100644 --- a/lms/djangoapps/courseware/features/courseware.py +++ b/lms/djangoapps/courseware/features/courseware.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from lettuce.django import django_url diff --git a/lms/djangoapps/courseware/features/courseware_common.py b/lms/djangoapps/courseware/features/courseware_common.py index 6aa9559e65..4e9aa3fb7b 100644 --- a/lms/djangoapps/courseware/features/courseware_common.py +++ b/lms/djangoapps/courseware/features/courseware_common.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step diff --git a/lms/djangoapps/courseware/features/homepage.py b/lms/djangoapps/courseware/features/homepage.py index 442098c161..62e9096e70 100644 --- a/lms/djangoapps/courseware/features/homepage.py +++ b/lms/djangoapps/courseware/features/homepage.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from nose.tools import assert_in diff --git a/lms/djangoapps/courseware/features/login.py b/lms/djangoapps/courseware/features/login.py index 3e3c0efbc4..bc90ea301c 100644 --- a/lms/djangoapps/courseware/features/login.py +++ b/lms/djangoapps/courseware/features/login.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import step, world from django.contrib.auth.models import User diff --git a/lms/djangoapps/courseware/features/openended.py b/lms/djangoapps/courseware/features/openended.py index 2f14b808a3..d848eb55d7 100644 --- a/lms/djangoapps/courseware/features/openended.py +++ b/lms/djangoapps/courseware/features/openended.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from lettuce.django import django_url from nose.tools import assert_equals, assert_in diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py index bdd9062ef3..b25d606c4e 100644 --- a/lms/djangoapps/courseware/features/problems.py +++ b/lms/djangoapps/courseware/features/problems.py @@ -2,6 +2,8 @@ Steps for problem.feature lettuce tests ''' +#pylint: disable=C0111 +#pylint: disable=W0621 from lettuce import world, step from lettuce.django import django_url diff --git a/lms/djangoapps/courseware/features/registration.py b/lms/djangoapps/courseware/features/registration.py index 63f044b16f..72bde65f99 100644 --- a/lms/djangoapps/courseware/features/registration.py +++ b/lms/djangoapps/courseware/features/registration.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from lettuce.django import django_url from common import TEST_COURSE_ORG, TEST_COURSE_NAME diff --git a/lms/djangoapps/courseware/features/signup.py b/lms/djangoapps/courseware/features/signup.py index d9edcb215b..5ba385ef54 100644 --- a/lms/djangoapps/courseware/features/signup.py +++ b/lms/djangoapps/courseware/features/signup.py @@ -1,5 +1,7 @@ -from lettuce import world, step +#pylint: disable=C0111 +#pylint: disable=W0621 +from lettuce import world, step @step('I fill in "([^"]*)" on the registration form with "([^"]*)"$') def when_i_fill_in_field_on_the_registration_form_with_value(step, field, value): diff --git a/lms/djangoapps/courseware/features/smart-accordion.py b/lms/djangoapps/courseware/features/smart-accordion.py index 8240a13905..63408d7683 100644 --- a/lms/djangoapps/courseware/features/smart-accordion.py +++ b/lms/djangoapps/courseware/features/smart-accordion.py @@ -1,3 +1,6 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from lettuce import world, step from re import sub from nose.tools import assert_equals diff --git a/lms/djangoapps/courseware/features/xqueue_setup.py b/lms/djangoapps/courseware/features/xqueue_setup.py index d6d7a13a5c..90a68961ee 100644 --- a/lms/djangoapps/courseware/features/xqueue_setup.py +++ b/lms/djangoapps/courseware/features/xqueue_setup.py @@ -1,9 +1,11 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + from courseware.mock_xqueue_server.mock_xqueue_server import MockXQueueServer from lettuce import before, after, world from django.conf import settings import threading - @before.all def setup_mock_xqueue_server(): From 586f566b4276b74756a0ce3bfe258ba979a45401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Tue, 26 Mar 2013 11:54:06 -0400 Subject: [PATCH 083/483] Use advertised_start as a simple string LMS Lighthouse [#297] --- common/lib/xmodule/xmodule/course_module.py | 12 ++++++++---- common/lib/xmodule/xmodule/fields.py | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index b1e5fa02c8..7999f8d6da 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -7,6 +7,8 @@ import requests import time from datetime import datetime +import dateutil.parser + from xmodule.modulestore import Location from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.timeparse import parse_time @@ -150,7 +152,7 @@ class CourseFields(object): enrollment_end = Date(help="Date that enrollment for this class is closed", scope=Scope.settings) start = Date(help="Start time when this module is visible", scope=Scope.settings) end = Date(help="Date that this class ends", scope=Scope.settings) - advertised_start = StringOrDate(help="Date that this course is advertised to start", scope=Scope.settings) + advertised_start = String(help="Date that this course is advertised to start", scope=Scope.settings) grading_policy = Object(help="Grading policy definition for this class", scope=Scope.content) show_calculator = Boolean(help="Whether to show the calculator in this course", default=False, scope=Scope.settings) display_name = String(help="Display name for this module", scope=Scope.settings) @@ -537,10 +539,12 @@ class CourseDescriptor(CourseFields, SequenceDescriptor): announcement = self.announcement if announcement is not None: announcement = to_datetime(announcement) - if self.advertised_start is None or isinstance(self.advertised_start, basestring): + + try: + start = dateutil.parser.parse(self.advertised_start) + except (ValueError, AttributeError): start = to_datetime(self.start) - else: - start = to_datetime(self.advertised_start) + now = to_datetime(time.gmtime()) return announcement, start, now diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index 99ead854ad..0abe850d68 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -23,6 +23,8 @@ class Date(ModelType): """ if field is None: return field + elif field is "": + return None elif isinstance(field, basestring): d = dateutil.parser.parse(field) return d.utctimetuple() From 7c68508b85c0a31b0c4745172558d3075cde0a23 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 26 Mar 2013 12:42:30 -0400 Subject: [PATCH 084/483] studio - finalized tender widget styling --- cms/static/sass/elements/_tender-widget.scss | 133 +++++++++++++++++-- cms/templates/widgets/tender.html | 1 + 2 files changed, 123 insertions(+), 11 deletions(-) diff --git a/cms/static/sass/elements/_tender-widget.scss b/cms/static/sass/elements/_tender-widget.scss index fce62b8675..4d2cdea373 100644 --- a/cms/static/sass/elements/_tender-widget.scss +++ b/cms/static/sass/elements/_tender-widget.scss @@ -23,7 +23,7 @@ #tender_closer { color: $blue-l2 !important; - margin-top: 15px; + margin-top: 10px; margin-right: 5px; text-transform: uppercase; @@ -66,6 +66,7 @@ .widget-layout .content { overflow: auto; + height: auto !important; padding: 20px; } @@ -110,10 +111,20 @@ width: 97%; } +.widget-layout p.note { + text-align: right !important; + display: inline-block !important; + position: absolute !important; + right: -130px !important; + top: -5px !important; + font-size: 13px !important; + opacity: 0.80; +} + .widget-layout .form-actions { - border-top: 1px solid #ccc; - margin-top: 10px; - padding-top: 10px; + margin: 15px 0; + border: none; + padding: 0; } .widget-layout dl.form { @@ -124,19 +135,119 @@ padding-bottom: 10px; } -.widget-layout #brain_buster_captcha { +.widget-layout dl.form:last-child { + border: none; + padding-bottom: 0; + margin-bottom: 20px; +} +.widget-layout dl.form dt, .widget-layout dl.form dd { + display: inline-block; + vertical-align: middle; +} + +.widget-layout dl.form dt { + margin-right: 15px; + width: 70px; +} + +.widget-layout dl.form dd { + width: 65%; + position: relative; } // specific elements .widget-layout #discussion_body { + +} + +.widget-layout #discussion_body:before { + content: "What Question or Feedback Would You Like to Share?"; + display: block; + font-size: 14px; + margin-bottom: 5px; + color: #4c4c4c; + font-weight: 500; +} + + +.widget-layout dl#brain_buster_captcha { + float: none; + width: 100%; + border-top: 1px solid #f2f2f2; + margin-top: 10px; + padding-top: 10px; +} + +.widget-layout dl#brain_buster_captcha dd { + display: block !important; +} + +.widget-layout dl#brain_buster_captcha dd label { + display: block; + margin: 0 15px 0 0 !important; +} + +.widget-layout dl#brain_buster_captcha dd #captcha_answer { + display: block; + width: 97%%; +} + +.widget-layout .form-actions .btn-post_topic { + display: block; + width: 100%; + height: auto !important; + font-size: 16px; + font-weight: 700; + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset,0 0 0 rgba(0,0,0,0); + -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset,0 0 0 rgba(0,0,0,0); + box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset,0 0 0 rgba(0,0,0,0); + -webkit-transition-property: background-color,0.15s; + -moz-transition-property: background-color,0.15s; + -ms-transition-property: background-color,0.15s; + -o-transition-property: background-color,0.15s; + transition-property: background-color,0.15s; + -webkit-transition-duration: box-shadow,0.15s; + -moz-transition-duration: box-shadow,0.15s; + -ms-transition-duration: box-shadow,0.15s; + -o-transition-duration: box-shadow,0.15s; + transition-duration: box-shadow,0.15s; + -webkit-transition-timing-function: ease-out; + -moz-transition-timing-function: ease-out; + -ms-transition-timing-function: ease-out; + -o-transition-timing-function: ease-out; + transition-timing-function: ease-out; + -webkit-transition-delay: 0; + -moz-transition-delay: 0; + -ms-transition-delay: 0; + -o-transition-delay: 0; + transition-delay: 0; + border: 1px solid #34854c; + border-radius: 3px; + background-color: rgba(255,255,255,0.3); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(255,255,255,0.3)),color-stop(100%, rgba(255,255,255,0))); + background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.3),rgba(255,255,255,0)); + background-image: -moz-linear-gradient(top, rgba(255,255,255,0.3),rgba(255,255,255,0)); + background-image: -ms-linear-gradient(top, rgba(255,255,255,0.3),rgba(255,255,255,0)); + background-image: -o-linear-gradient(top, rgba(255,255,255,0.3),rgba(255,255,255,0)); + background-image: linear-gradient(top, rgba(255,255,255,0.3),rgba(255,255,255,0)); + background-color: #25b85a; + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset; + -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset; + box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset; + color: #fff; + text-align: center; + margin-top: 20px; + padding: 10px 20px; +} + +.widget-layout .form-actions #private-discussion-opt { + float: none; + text-align: left; margin: 0 0 15px 0; } -.widget-layout .category dt, .widget-layout .category dd { - display: inline-block !important; -} - -.widget-layout .category dt { - margin-right: 15px !important; +.widget-layout .form-actions .btn-post_topic:hover, .widget-layout .form-actions .btn-post_topic:active { + background-color: #16ca57; + color: #fff; } \ No newline at end of file diff --git a/cms/templates/widgets/tender.html b/cms/templates/widgets/tender.html index 300b71701c..27cc574490 100644 --- a/cms/templates/widgets/tender.html +++ b/cms/templates/widgets/tender.html @@ -1,5 +1,6 @@ % if user.is_authenticated(): Provide Feedback + @@ -53,11 +51,14 @@ document.location.protocol + '//www.youtube.com/player_api">\x3C/script>'); - <%block name="content"> - <%include file="widgets/footer.html" /> +
    + <%include file="widgets/header.html" /> + <%block name="content"> + <%include file="widgets/sock.html" /> + <%include file="widgets/footer.html" /> +
    + <%include file="widgets/tender.html" /> <%block name="jsextra"> - - - + \ No newline at end of file diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html index f9166bf166..cbf436ecc5 100644 --- a/cms/templates/course_info.html +++ b/cms/templates/course_info.html @@ -80,5 +80,4 @@
    -
    \ No newline at end of file diff --git a/cms/templates/howitworks.html b/cms/templates/howitworks.html index 1cf9b17710..7a819fceba 100644 --- a/cms/templates/howitworks.html +++ b/cms/templates/howitworks.html @@ -151,7 +151,7 @@
    Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.
    - + close modal @@ -164,7 +164,7 @@
    Quickly create videos, text snippets, inline discussions, and a variety of problem types.
    - + close modal @@ -177,7 +177,7 @@
    Simply set the date of a section or subsection, and Studio will publish it to your students for you.
    - + close modal diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index a3674cfe20..9ff98fa26b 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -16,13 +16,13 @@ --> diff --git a/cms/templates/widgets/sock.html b/cms/templates/widgets/sock.html new file mode 100644 index 0000000000..ff5f9c9ad4 --- /dev/null +++ b/cms/templates/widgets/sock.html @@ -0,0 +1,8 @@ +<%! from django.core.urlresolvers import reverse %> +% if user.is_authenticated(): +
    +
    +

    Sock!

    +
    +
    +% endif \ No newline at end of file From 25acab497e05ab6d9883726d0cba2ec5146fa6ae Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 01:33:04 -0400 Subject: [PATCH 103/483] studio - corrected JQ selector for smoothscrolling in-page links --- cms/static/js/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 7135e2780c..211981b05a 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -81,7 +81,7 @@ $(document).ready(function () { }); // general link management - smooth scrolling page links - $('a[rel*="view"][href|="#"]').bind('click', smoothScrollLink); + $('a[rel*="view"][href^="#"]').bind('click', smoothScrollLink); // tender feedback window scrolling $('a.show-tender').bind('click', smoothScrollTop); From 2120481738489d872db41916b75b0336a77a3a9e Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 01:34:25 -0400 Subject: [PATCH 104/483] studio - corrected JQ selector for smoothscrolling in-page links --- cms/static/js/base.js | 4 ++-- cms/templates/howitworks.html | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index bd8dc0bae8..7466233331 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -81,7 +81,7 @@ $(document).ready(function () { }); // general link management - smooth scrolling page links - $('a[rel*="view"]').bind('click', linkSmoothScroll); + $('a[rel*="view"][href^="#"]').bind('click', smoothScrollLink); // toggling overview section details @@ -148,7 +148,7 @@ $(document).ready(function () { }); }); -function linkSmoothScroll(e) { +function smoothScrollLink(e) { (e).preventDefault(); $.smoothScroll({ diff --git a/cms/templates/howitworks.html b/cms/templates/howitworks.html index 1cf9b17710..7a819fceba 100644 --- a/cms/templates/howitworks.html +++ b/cms/templates/howitworks.html @@ -151,7 +151,7 @@
    Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.
    - + close modal @@ -164,7 +164,7 @@
    Quickly create videos, text snippets, inline discussions, and a variety of problem types.
    - + close modal @@ -177,7 +177,7 @@
    Simply set the date of a section or subsection, and Studio will publish it to your students for you.
    - + close modal From 74439746cc804fc2dfe9bc6b679f0a714093aa74 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 01:38:36 -0400 Subject: [PATCH 105/483] studio - made provide feedback conditional for logged in users --- cms/templates/widgets/footer.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index 9ff98fa26b..18ecf2bc39 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -18,9 +18,11 @@ + % if user.is_authenticated(): + + % endif From e3c646492c1147f74fec0e9df45f58f1c9fe892e Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 01:39:24 -0400 Subject: [PATCH 106/483] studio - made provide feedback conditional for logged in users --- cms/templates/widgets/footer.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index a3674cfe20..c0cf8f73a6 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -18,9 +18,11 @@ + % if user.is_authenticated(): + + % endif From 2c0e5b82ff2535770a5ca605aa1b1bd521c756d4 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 27 Mar 2013 07:29:22 -0400 Subject: [PATCH 107/483] Return a 403 when an anonymous user attempts to hit modx_dispatch. Fixes https://www.pivotaltracker.com/story/show/46916015 and https://www.pivotaltracker.com/story/show/46916029 --- lms/djangoapps/courseware/module_render.py | 4 +++ .../courseware/tests/test_module_render.py | 31 +++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 973940d784..4747f7b341 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -8,6 +8,7 @@ from functools import partial from django.conf import settings from django.contrib.auth.models import User +from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.http import Http404 from django.http import HttpResponse @@ -412,6 +413,9 @@ def modx_dispatch(request, dispatch, location, course_id): if not Location.is_valid(location): raise Http404("Invalid location") + if not request.user.is_authenticated(): + raise PermissionDenied + # Check for submitted files and basic file size checks p = request.POST.copy() if request.FILES: diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 3a3a7ac5ea..90ca796a2f 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -1,14 +1,7 @@ -import logging -from mock import MagicMock, patch +from mock import MagicMock import json -import factory -import unittest -from nose.tools import set_trace -from django.http import Http404, HttpResponse, HttpRequest -from django.conf import settings -from django.contrib.auth.models import User -from django.test.client import Client +from django.http import Http404, HttpResponse from django.conf import settings from django.test import TestCase from django.test.client import RequestFactory @@ -16,13 +9,9 @@ from django.core.urlresolvers import reverse from django.test.utils import override_settings from xmodule.modulestore.exceptions import ItemNotFoundError -from xmodule.exceptions import NotFoundError -from xmodule.modulestore import Location import courseware.module_render as render -from xmodule.modulestore.django import modulestore, _MODULESTORES -from xmodule.seq_module import SequenceModule +from xmodule.modulestore.django import modulestore from courseware.tests.tests import PageLoader -from student.models import Registration from courseware.model_data import ModelDataCache from .factories import UserFactory @@ -52,7 +41,6 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) class ModuleRenderTestCase(PageLoader): def setUp(self): self.location = ['i4x', 'edX', 'toy', 'chapter', 'Overview'] - self._MODULESTORES = {} self.course_id = 'edX/toy/2012_Fall' self.toy_course = modulestore().get_course(self.course_id) @@ -104,12 +92,23 @@ class ModuleRenderTestCase(PageLoader): self.assertEquals(render.get_score_bucket(11, 10), 'incorrect') self.assertEquals(render.get_score_bucket(-1, 10), 'incorrect') + def test_anonymous_modx_dispatch(self): + dispatch_url = reverse( + 'modx_dispatch', + args=[ + 'edX/toy/2012_Fall', + 'i4x://edX/toy/videosequence/Toy_Videos', + 'goto_position' + ] + ) + response = self.client.post(dispatch_url, {'position': 2}) + self.assertEquals(403, response.status_code) + @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestTOC(TestCase): """Check the Table of Contents for a course""" def setUp(self): - self._MODULESTORES = {} # Toy courses should be loaded self.course_name = 'edX/toy/2012_Fall' From 521843876efb005303e8ff7423442eb9830ab99e Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 27 Mar 2013 08:10:25 -0400 Subject: [PATCH 108/483] Make the django_comment_client return errors that can't be parsed as JSON just as simple strings when in an ajax context --- .../django_comment_client/middleware.py | 16 +++++++++- .../tests/test_middleware.py | 32 +++++++++---------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lms/djangoapps/django_comment_client/middleware.py b/lms/djangoapps/django_comment_client/middleware.py index abf2d40cab..b9efc1589e 100644 --- a/lms/djangoapps/django_comment_client/middleware.py +++ b/lms/djangoapps/django_comment_client/middleware.py @@ -1,10 +1,24 @@ from comment_client import CommentClientError from django_comment_client.utils import JsonError import json +import logging + +log = logging.getLogger(__name__) class AjaxExceptionMiddleware(object): + """ + Middleware that captures CommentClientErrors during ajax requests + and tranforms them into json responses + """ def process_exception(self, request, exception): + """ + Processes CommentClientErrors in ajax requests. If the request is an ajax request, + returns a http response that encodes the error as json + """ if isinstance(exception, CommentClientError) and request.is_ajax(): - return JsonError(json.loads(exception.message)) + try: + return JsonError(json.loads(exception.message)) + except ValueError: + return JsonError(exception.message) return None diff --git a/lms/djangoapps/django_comment_client/tests/test_middleware.py b/lms/djangoapps/django_comment_client/tests/test_middleware.py index 55e4c72c75..ab9517c160 100644 --- a/lms/djangoapps/django_comment_client/tests/test_middleware.py +++ b/lms/djangoapps/django_comment_client/tests/test_middleware.py @@ -1,7 +1,3 @@ -import string -import random -import collections - from django.test import TestCase import comment_client @@ -13,17 +9,19 @@ class AjaxExceptionTestCase(TestCase): # TODO: check whether the correct error message is produced. # The error message should be the same as the argument to CommentClientError - def setUp(self): - self.a = middleware.AjaxExceptionMiddleware() - self.request1 = django.http.HttpRequest() - self.request0 = django.http.HttpRequest() - self.exception1 = comment_client.CommentClientError('{}') - self.exception0 = ValueError() - self.request1.META['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest" - self.request0.META['HTTP_X_REQUESTED_WITH'] = "SHADOWFAX" + def setUp(self): + self.a = middleware.AjaxExceptionMiddleware() + self.request1 = django.http.HttpRequest() + self.request0 = django.http.HttpRequest() + self.exception1 = comment_client.CommentClientError('{}') + self.exception2 = comment_client.CommentClientError('Foo!') + self.exception0 = ValueError() + self.request1.META['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest" + self.request0.META['HTTP_X_REQUESTED_WITH'] = "SHADOWFAX" - def test_process_exception(self): - self.assertIsInstance(self.a.process_exception(self.request1, self.exception1), middleware.JsonError) - self.assertIsNone(self.a.process_exception(self.request1, self.exception0)) - self.assertIsNone(self.a.process_exception(self.request0, self.exception1)) - self.assertIsNone(self.a.process_exception(self.request0, self.exception0)) + def test_process_exception(self): + self.assertIsInstance(self.a.process_exception(self.request1, self.exception1), middleware.JsonError) + self.assertIsInstance(self.a.process_exception(self.request1, self.exception2), middleware.JsonError) + self.assertIsNone(self.a.process_exception(self.request1, self.exception0)) + self.assertIsNone(self.a.process_exception(self.request0, self.exception1)) + self.assertIsNone(self.a.process_exception(self.request0, self.exception0)) From 285e3ee1edfbb5cbb22f821b05b7a752aab25c73 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 10:49:47 -0400 Subject: [PATCH 109/483] Capa response now displays full stack trace on student input error if the user is a staff member. Otherwise, it displays just the exception message. --- common/lib/capa/capa/responsetypes.py | 7 +++--- common/lib/xmodule/xmodule/capa_module.py | 16 +++++++++++- .../xmodule/xmodule/tests/test_capa_module.py | 25 +++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 2c556211f8..465c212b30 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -17,6 +17,7 @@ import logging import numbers import numpy import os +import sys import random import re import requests @@ -1233,9 +1234,9 @@ def sympy_check2(): log.debug(msg, exc_info=True) log.debug(traceback.format_exc()) - # Notify student - raise StudentInputError( - "Error: Problem could not be evaluated with your input") + # Notify student with a student input error + _, _, traceback_obj = sys.exc_info() + raise StudentInputError, StudentInputError(err.message), traceback_obj #----------------------------------------------------------------------------- diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index da8b5b4f96..203e14fdc1 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -725,9 +725,23 @@ class CapaModule(CapaFields, XModule): try: correct_map = self.lcp.grade_answers(answers) self.set_state_from_lcp() + except StudentInputError as inst: log.exception("StudentInputError in capa_module:problem_check") - return {'success': inst.message} + + # If the user is a staff member, include + # the full exception, including traceback, + # in the response + if self.system.user_is_staff: + msg = traceback.format_exc() + + # Otherwise, display just the error message, + # without a stack trace + else: + msg = inst.message + + return {'success': msg } + except Exception, err: if self.system.DEBUG: msg = "Error checking problem: " + str(err) diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index d2458cb3d0..d769b65914 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -505,6 +505,9 @@ class CapaModuleTest(unittest.TestCase): def test_check_problem_student_input_error(self): module = CapaFactory.create(attempts=1) + # Ensure that the user is NOT staff + module.system.user_is_staff = False + # Simulate a student input exception with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: mock_grade.side_effect = capa.responsetypes.StudentInputError('test error') @@ -515,10 +518,32 @@ class CapaModuleTest(unittest.TestCase): # Expect an AJAX alert message in 'success' self.assertTrue('test error' in result['success']) + # We do NOT include traceback information for + # a non-staff user + self.assertFalse('Traceback' in result['success']) + # Expect that the number of attempts is NOT incremented self.assertEqual(module.attempts, 1) + def test_check_problem_student_input_error_with_staff_user(self): + module = CapaFactory.create(attempts=1) + # Ensure that the user IS staff + module.system.user_is_staff = True + + # Simulate a student input exception + with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: + mock_grade.side_effect = capa.responsetypes.StudentInputError('test error') + + get_request_dict = { CapaFactory.input_key(): '3.14'} + result = module.check_problem(get_request_dict) + + # Expect an AJAX alert message in 'success' + self.assertTrue('test error' in result['success']) + + # We DO include traceback information for staff users + self.assertTrue('Traceback' in result['success']) + def test_reset_problem(self): module = CapaFactory.create(done=True) module.new_lcp = Mock(wraps=module.new_lcp) From 754e30240d7c62c7efd80c38d552c4d168d23262 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 11:01:25 -0400 Subject: [PATCH 110/483] studio - adjusting tender widget window height based on field removal --- cms/static/sass/elements/_tender-widget.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cms/static/sass/elements/_tender-widget.scss b/cms/static/sass/elements/_tender-widget.scss index 146d5b4111..478489d0e8 100644 --- a/cms/static/sass/elements/_tender-widget.scss +++ b/cms/static/sass/elements/_tender-widget.scss @@ -9,6 +9,7 @@ #tender_window { @include border-radius(3px); @include box-shadow(0 2px 3px $shadow); + height: 650px !important; background: $white !important; border: 1px solid $gray; } @@ -72,7 +73,7 @@ .widget-layout .flash { margin: -10px 0 15px 0; - padding: 5px 10px !important; + padding: 10px 20px !important; background-image: none !important; } From 8252ba15df79f3f2b213d8afed58c3a152f0bb2b Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 11:02:30 -0400 Subject: [PATCH 111/483] Changed error message for StudentInputError for non-staff to a generic message. Otherwise, the default exception messages are cryptic for students (e.g. "cannot convert string to float") --- common/lib/xmodule/xmodule/capa_module.py | 4 ++-- common/lib/xmodule/xmodule/tests/test_capa_module.py | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 203e14fdc1..c3159bb3ee 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -735,10 +735,10 @@ class CapaModule(CapaFields, XModule): if self.system.user_is_staff: msg = traceback.format_exc() - # Otherwise, display just the error message, + # Otherwise, display just an error message, # without a stack trace else: - msg = inst.message + msg = "Error: Problem could not be evaluated with your input" return {'success': msg } diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index d769b65914..b5e1ff311c 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -516,11 +516,8 @@ class CapaModuleTest(unittest.TestCase): result = module.check_problem(get_request_dict) # Expect an AJAX alert message in 'success' - self.assertTrue('test error' in result['success']) - - # We do NOT include traceback information for - # a non-staff user - self.assertFalse('Traceback' in result['success']) + expected_msg = 'Error: Problem could not be evaluated with your input' + self.assertEqual(expected_msg, result['success']) # Expect that the number of attempts is NOT incremented self.assertEqual(module.attempts, 1) From 5bc44e50da28ca31f10bbf447fd112c948717f86 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 11:13:31 -0400 Subject: [PATCH 112/483] Changed error messages to account for NumericalResponse formatting, which is the only other response type to use StudentInputError. --- common/lib/capa/capa/responsetypes.py | 2 +- common/lib/xmodule/xmodule/capa_module.py | 2 +- common/lib/xmodule/xmodule/tests/test_capa_module.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 465c212b30..08cfa8b9d9 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -834,7 +834,7 @@ class NumericalResponse(LoncapaResponse): import sys type, value, traceback = sys.exc_info() - raise StudentInputError, ("Invalid input: could not interpret '%s' as a number" % + raise StudentInputError, ("Could not interpret '%s' as a number" % cgi.escape(student_answer)), traceback if correct: diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index c3159bb3ee..773ae73d59 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -738,7 +738,7 @@ class CapaModule(CapaFields, XModule): # Otherwise, display just an error message, # without a stack trace else: - msg = "Error: Problem could not be evaluated with your input" + msg = "Error: %s" % str(inst.message) return {'success': msg } diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index b5e1ff311c..3617086f85 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -516,7 +516,7 @@ class CapaModuleTest(unittest.TestCase): result = module.check_problem(get_request_dict) # Expect an AJAX alert message in 'success' - expected_msg = 'Error: Problem could not be evaluated with your input' + expected_msg = 'Error: test error' self.assertEqual(expected_msg, result['success']) # Expect that the number of attempts is NOT incremented From 0f5e8c5f3bb8acbe8b4396ab172ca1740b7b89fd Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 11:17:21 -0400 Subject: [PATCH 113/483] pep8 fixes --- common/lib/capa/capa/responsetypes.py | 8 ++--- common/lib/xmodule/xmodule/capa_module.py | 10 +++--- .../xmodule/xmodule/tests/test_capa_module.py | 32 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 08cfa8b9d9..e79399c5fc 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1141,9 +1141,9 @@ def sympy_check2(): correct = [] messages = [] for input_dict in input_list: - correct.append('correct' + correct.append('correct' if input_dict['ok'] else 'incorrect') - msg = (self.clean_message_html(input_dict['msg']) + msg = (self.clean_message_html(input_dict['msg']) if 'msg' in input_dict else None) messages.append(msg) @@ -1168,7 +1168,7 @@ def sympy_check2(): correct_map.set_overall_message(overall_message) for k in range(len(idset)): - npoints = (self.maxpoints[idset[k]] + npoints = (self.maxpoints[idset[k]] if correct[k] == 'correct' else 0) correct_map.set(idset[k], correct[k], msg=messages[k], npoints=npoints) @@ -2085,7 +2085,7 @@ class AnnotationResponse(LoncapaResponse): option_scoring = dict([(option['id'], { 'correctness': choices.get(option['choice']), 'points': scoring.get(option['choice']) - }) for option in self._find_options(inputfield) ]) + }) for option in self._find_options(inputfield)]) scoring_map[inputfield.get('id')] = option_scoring diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 773ae73d59..af29c4c2fe 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -576,7 +576,7 @@ class CapaModule(CapaFields, XModule): # save any state changes that may occur self.set_state_from_lcp() return response - + def get_answer(self, get): ''' @@ -731,7 +731,7 @@ class CapaModule(CapaFields, XModule): # If the user is a staff member, include # the full exception, including traceback, - # in the response + # in the response if self.system.user_is_staff: msg = traceback.format_exc() @@ -740,7 +740,7 @@ class CapaModule(CapaFields, XModule): else: msg = "Error: %s" % str(inst.message) - return {'success': msg } + return {'success': msg} except Exception, err: if self.system.DEBUG: @@ -792,7 +792,7 @@ class CapaModule(CapaFields, XModule): event_info['answers'] = answers # Too late. Cannot submit - if self.closed() and not self.max_attempts ==0: + if self.closed() and not self.max_attempts == 0: event_info['failure'] = 'closed' self.system.track_function('save_problem_fail', event_info) return {'success': False, @@ -812,7 +812,7 @@ class CapaModule(CapaFields, XModule): self.system.track_function('save_problem_success', event_info) msg = "Your answers have been saved" - if not self.max_attempts ==0: + if not self.max_attempts == 0: msg += " but not graded. Hit 'Check' to grade them." return {'success': True, 'msg': msg} diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 3617086f85..18d20a2756 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -407,7 +407,7 @@ class CapaModuleTest(unittest.TestCase): mock_html.return_value = "Test HTML" # Check the problem - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.check_problem(get_request_dict) # Expect that the problem is marked correct @@ -428,7 +428,7 @@ class CapaModuleTest(unittest.TestCase): mock_is_correct.return_value = False # Check the problem - get_request_dict = { CapaFactory.input_key(): '0'} + get_request_dict = {CapaFactory.input_key(): '0'} result = module.check_problem(get_request_dict) # Expect that the problem is marked correct @@ -446,7 +446,7 @@ class CapaModuleTest(unittest.TestCase): with patch('xmodule.capa_module.CapaModule.closed') as mock_closed: mock_closed.return_value = True with self.assertRaises(xmodule.exceptions.NotFoundError): - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} module.check_problem(get_request_dict) # Expect that number of attempts NOT incremented @@ -492,7 +492,7 @@ class CapaModuleTest(unittest.TestCase): mock_is_queued.return_value = True mock_get_queuetime.return_value = datetime.datetime.now() - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.check_problem(get_request_dict) # Expect an AJAX alert message in 'success' @@ -512,7 +512,7 @@ class CapaModuleTest(unittest.TestCase): with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: mock_grade.side_effect = capa.responsetypes.StudentInputError('test error') - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.check_problem(get_request_dict) # Expect an AJAX alert message in 'success' @@ -532,7 +532,7 @@ class CapaModuleTest(unittest.TestCase): with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: mock_grade.side_effect = capa.responsetypes.StudentInputError('test error') - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.check_problem(get_request_dict) # Expect an AJAX alert message in 'success' @@ -540,7 +540,7 @@ class CapaModuleTest(unittest.TestCase): # We DO include traceback information for staff users self.assertTrue('Traceback' in result['success']) - + def test_reset_problem(self): module = CapaFactory.create(done=True) module.new_lcp = Mock(wraps=module.new_lcp) @@ -595,11 +595,11 @@ class CapaModuleTest(unittest.TestCase): module = CapaFactory.create(done=False) # Save the problem - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.save_problem(get_request_dict) # Expect that answers are saved to the problem - expected_answers = { CapaFactory.answer_key(): '3.14'} + expected_answers = {CapaFactory.answer_key(): '3.14'} self.assertEqual(module.lcp.student_answers, expected_answers) # Expect that the result is success @@ -614,7 +614,7 @@ class CapaModuleTest(unittest.TestCase): mock_closed.return_value = True # Try to save the problem - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.save_problem(get_request_dict) # Expect that the result is failure @@ -625,7 +625,7 @@ class CapaModuleTest(unittest.TestCase): module = CapaFactory.create(rerandomize='always', done=True) # Try to save - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.save_problem(get_request_dict) # Expect that we cannot save @@ -636,7 +636,7 @@ class CapaModuleTest(unittest.TestCase): module = CapaFactory.create(rerandomize='never', done=True) # Try to save - get_request_dict = { CapaFactory.input_key(): '3.14'} + get_request_dict = {CapaFactory.input_key(): '3.14'} result = module.save_problem(get_request_dict) # Expect that we succeed @@ -648,7 +648,7 @@ class CapaModuleTest(unittest.TestCase): # Just in case, we also check what happens if we have # more attempts than allowed. attempts = random.randint(1, 10) - module = CapaFactory.create(attempts=attempts -1, max_attempts=attempts) + module = CapaFactory.create(attempts=attempts - 1, max_attempts=attempts) self.assertEqual(module.check_button_name(), "Final Check") module = CapaFactory.create(attempts=attempts, max_attempts=attempts) @@ -658,14 +658,14 @@ class CapaModuleTest(unittest.TestCase): self.assertEqual(module.check_button_name(), "Final Check") # Otherwise, button name is "Check" - module = CapaFactory.create(attempts=attempts -2, max_attempts=attempts) + module = CapaFactory.create(attempts=attempts - 2, max_attempts=attempts) self.assertEqual(module.check_button_name(), "Check") - module = CapaFactory.create(attempts=attempts -3, max_attempts=attempts) + module = CapaFactory.create(attempts=attempts - 3, max_attempts=attempts) self.assertEqual(module.check_button_name(), "Check") # If no limit on attempts, then always show "Check" - module = CapaFactory.create(attempts=attempts -3) + module = CapaFactory.create(attempts=attempts - 3) self.assertEqual(module.check_button_name(), "Check") module = CapaFactory.create(attempts=0) From 6edee96caf528b73f2d9c097800ba8b867942de2 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 11:24:16 -0400 Subject: [PATCH 114/483] Added "Staff Debug Info" prefix to traceback message. --- common/lib/xmodule/xmodule/capa_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index af29c4c2fe..d7346faa67 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -733,7 +733,7 @@ class CapaModule(CapaFields, XModule): # the full exception, including traceback, # in the response if self.system.user_is_staff: - msg = traceback.format_exc() + msg = "Staff debug info: %s" % traceback.format_exc() # Otherwise, display just an error message, # without a stack trace From 7101c76016e4c42b18ba5858556b836da6fde66b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 27 Mar 2013 12:02:32 -0400 Subject: [PATCH 115/483] comment on rewrite links change --- .../combined_open_ended_modulev1.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index f88fd9ab82..6fe37b9525 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -365,6 +365,10 @@ class CombinedOpenEndedV1Module(): html = self.current_task.get_html(self.system) return_html = html try: + #Without try except block, get this error: + # File "/home/vik/mitx_all/mitx/common/lib/xmodule/xmodule/x_module.py", line 263, in rewrite_content_links + # if link.startswith(XASSET_SRCREF_PREFIX): + # Placing try except so that if the error is fixed, this code will start working again. return_html = rewrite_links(html, self.rewrite_content_links) except: pass @@ -786,7 +790,7 @@ class CombinedOpenEndedV1Descriptor(): template_dir_name = "combinedopenended" def __init__(self, system): - self.system =system + self.system = system @classmethod def definition_from_xml(cls, xml_object, system): From 3a4bdf19fb6733dae57b557223481601dbbe8efb Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 12:26:49 -0400 Subject: [PATCH 116/483] studio - tweaking footer navigation for tender widget label --- cms/templates/widgets/footer.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index c0cf8f73a6..c5fc81957f 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -20,12 +20,9 @@ % if user.is_authenticated(): - % endif - + % endif From 15ea32b095abe4d033075640121ad418ced0179d Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 12:53:58 -0400 Subject: [PATCH 117/483] Fixed bug 294, caused by unicode encoding error when creating logging strings. Added unit tests that verify the fix. --- common/djangoapps/student/tests/test_login.py | 107 ++++++++++++++++++ common/djangoapps/student/views.py | 8 +- 2 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 common/djangoapps/student/tests/test_login.py diff --git a/common/djangoapps/student/tests/test_login.py b/common/djangoapps/student/tests/test_login.py new file mode 100644 index 0000000000..dda58a4462 --- /dev/null +++ b/common/djangoapps/student/tests/test_login.py @@ -0,0 +1,107 @@ +from django.test import TestCase +from django.test.client import Client +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from student.models import Registration, UserProfile +import json + +class LoginTest(TestCase): + ''' + Test student.views.login_user() view + ''' + + def setUp(self): + + # Create one user and save it to the database + self.user = User.objects.create_user('test', 'test@edx.org', 'test_password') + self.user.is_active = True + self.user.save() + + # Create a registration for the user + Registration().register(self.user) + + # Create a profile for the user + UserProfile(user=self.user).save() + + # Create the test client + self.client = Client() + + # Store the login url + self.url = reverse('login') + + def test_login_success(self): + response = self._login_response('test@edx.org', 'test_password') + self._assert_response(response, success=True) + + def test_login_success_unicode_email(self): + unicode_email = u'test@edx.org' + unichr(40960) + + self.user.email = unicode_email + self.user.save() + + response = self._login_response(unicode_email, 'test_password') + self._assert_response(response, success=True) + + + def test_login_fail_no_user_exists(self): + response = self._login_response('not_a_user@edx.org', 'test_password') + self._assert_response(response, success=False, + value='Email or password is incorrect') + + def test_login_fail_wrong_password(self): + response = self._login_response('test@edx.org', 'wrong_password') + self._assert_response(response, success=False, + value='Email or password is incorrect') + + def test_login_not_activated(self): + + # De-activate the user + self.user.is_active = False + self.user.save() + + # Should now be unable to login + response = self._login_response('test@edx.org', 'test_password') + self._assert_response(response, success=False, + value="This account has not been activated") + + + def test_login_unicode_email(self): + unicode_email = u'test@edx.org' + unichr(40960) + response = self._login_response(unicode_email, 'test_password') + self._assert_response(response, success=False) + + def test_login_unicode_password(self): + unicode_password = u'test_password' + unichr(1972) + response = self._login_response('test@edx.org', unicode_password) + self._assert_response(response, success=False) + + def _login_response(self, email, password): + post_params = {'email': email, 'password': password} + return self.client.post(self.url, post_params) + + def _assert_response(self, response, success=None, value=None): + ''' + Assert that the response had status 200 and returned a valid + JSON-parseable dict. + + If success is provided, assert that the response had that + value for 'success' in the JSON dict. + + If value is provided, assert that the response contained that + value for 'value' in the JSON dict. + ''' + self.assertEqual(response.status_code, 200) + + try: + response_dict = json.loads(response.content) + except ValueError: + self.fail("Could not parse response content as JSON: %s" + % str(response.content)) + + if success is not None: + self.assertEqual(response_dict['success'], success) + + if value is not None: + msg = ("'%s' did not contain '%s'" % + (str(response_dict['value']), str(value))) + self.assertTrue(value in response_dict['value'], msg) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 5dbaf5d2c2..84730421e8 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -369,14 +369,14 @@ def login_user(request, error=""): try: user = User.objects.get(email=email) except User.DoesNotExist: - log.warning("Login failed - Unknown user email: {0}".format(email)) + log.warning(u"Login failed - Unknown user email: {0}".format(email)) return HttpResponse(json.dumps({'success': False, 'value': 'Email or password is incorrect.'})) # TODO: User error message username = user.username user = authenticate(username=username, password=password) if user is None: - log.warning("Login failed - password for {0} is invalid".format(email)) + log.warning(u"Login failed - password for {0} is invalid".format(email)) return HttpResponse(json.dumps({'success': False, 'value': 'Email or password is incorrect.'})) @@ -392,7 +392,7 @@ def login_user(request, error=""): log.critical("Login failed - Could not create session. Is memcached running?") log.exception(e) - log.info("Login success - {0} ({1})".format(username, email)) + log.info(u"Login success - {0} ({1})".format(username, email)) try_change_enrollment(request) @@ -400,7 +400,7 @@ def login_user(request, error=""): return HttpResponse(json.dumps({'success': True})) - log.warning("Login failed - Account not active for user {0}, resending activation".format(username)) + log.warning(u"Login failed - Account not active for user {0}, resending activation".format(username)) reactivation_email_for_user(user) not_activated_msg = "This account has not been activated. We have " + \ From 227a5e8266ddc72e9719eb2b6035a12ee0788c56 Mon Sep 17 00:00:00 2001 From: cahrens Date: Wed, 27 Mar 2013 12:56:06 -0400 Subject: [PATCH 118/483] Delete converters, move unit tests to test_fields, add new additional test cases. --- .../tests/test_course_settings.py | 75 +++-------------- .../models/settings/course_details.py | 28 +++++-- .../models/settings/course_grading.py | 2 - common/djangoapps/util/converters.py | 37 --------- common/lib/xmodule/xmodule/fields.py | 1 - .../lib/xmodule/xmodule/tests/test_fields.py | 80 +++++++++++++++++++ 6 files changed, 113 insertions(+), 110 deletions(-) delete mode 100644 common/djangoapps/util/converters.py create mode 100644 common/lib/xmodule/xmodule/tests/test_fields.py diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 2e7bc5db83..fe90ad18aa 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -1,8 +1,6 @@ import datetime import json import copy -from util import converters -from util.converters import jsdate_to_time from django.contrib.auth.models import User from django.test.client import Client @@ -15,69 +13,13 @@ from models.settings.course_details import (CourseDetails, from models.settings.course_grading import CourseGradingModel from contentstore.utils import get_modulestore -from django.test import TestCase from .utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from models.settings.course_metadata import CourseMetadata from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.django import modulestore -import time - - -# YYYY-MM-DDThh:mm:ss.s+/-HH:MM -class ConvertersTestCase(TestCase): - @staticmethod - def struct_to_datetime(struct_time): - return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, - struct_time.tm_mday, struct_time.tm_hour, - struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC()) - - def compare_dates(self, date1, date2, expected_delta): - dt1 = ConvertersTestCase.struct_to_datetime(date1) - dt2 = ConvertersTestCase.struct_to_datetime(date2) - self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-" - + str(date2) + "!=" + str(expected_delta)) - - def test_iso_to_struct(self): - '''Test conversion from iso compatible date strings to struct_time''' - self.compare_dates(converters.jsdate_to_time("2013-01-01"), - converters.jsdate_to_time("2012-12-31"), - datetime.timedelta(days=1)) - self.compare_dates(converters.jsdate_to_time("2013-01-01T00"), - converters.jsdate_to_time("2012-12-31T23"), - datetime.timedelta(hours=1)) - self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00"), - converters.jsdate_to_time("2012-12-31T23:59"), - datetime.timedelta(minutes=1)) - self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), - converters.jsdate_to_time("2012-12-31T23:59:59"), - datetime.timedelta(seconds=1)) - self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00Z"), - converters.jsdate_to_time("2012-12-31T23:59:59Z"), - datetime.timedelta(seconds=1)) - self.compare_dates( - converters.jsdate_to_time("2012-12-31T23:00:01-01:00"), - converters.jsdate_to_time("2013-01-01T00:00:00+01:00"), - datetime.timedelta(hours=1, seconds=1)) - - def test_struct_to_iso(self): - ''' - Test converting time reprs to iso dates - ''' - self.assertEqual( - converters.time_to_isodate( - time.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")), - "2012-12-31T23:59:59Z") - self.assertEqual( - converters.time_to_isodate( - jsdate_to_time("2012-12-31T23:59:59Z")), - "2012-12-31T23:59:59Z") - self.assertEqual( - converters.time_to_isodate( - jsdate_to_time("2012-12-31T23:00:01-01:00")), - "2013-01-01T00:00:01Z") - +from xmodule.fields import Date class CourseTestCase(ModuleStoreTestCase): def setUp(self): @@ -206,17 +148,24 @@ class CourseDetailsViewTest(CourseTestCase): self.assertEqual(details['intro_video'], encoded.get('intro_video', None), context + " intro_video not ==") self.assertEqual(details['effort'], encoded['effort'], context + " efforts not ==") + @staticmethod + def struct_to_datetime(struct_time): + return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, + struct_time.tm_mday, struct_time.tm_hour, + struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC()) + def compare_date_fields(self, details, encoded, context, field): if details[field] is not None: + date = Date() if field in encoded and encoded[field] is not None: - encoded_encoded = jsdate_to_time(encoded[field]) - dt1 = ConvertersTestCase.struct_to_datetime(encoded_encoded) + encoded_encoded = date.from_json(encoded[field]) + dt1 = CourseDetailsViewTest.struct_to_datetime(encoded_encoded) if isinstance(details[field], datetime.datetime): dt2 = details[field] else: - details_encoded = jsdate_to_time(details[field]) - dt2 = ConvertersTestCase.struct_to_datetime(details_encoded) + details_encoded = date.from_json(details[field]) + dt2 = CourseDetailsViewTest.struct_to_datetime(details_encoded) expected_delta = datetime.timedelta(0) self.assertEqual(dt1 - dt2, expected_delta, str(dt1) + "!=" + str(dt2) + " at " + context) diff --git a/cms/djangoapps/models/settings/course_details.py b/cms/djangoapps/models/settings/course_details.py index d3cd5fe164..09d57774ab 100644 --- a/cms/djangoapps/models/settings/course_details.py +++ b/cms/djangoapps/models/settings/course_details.py @@ -1,14 +1,14 @@ -from xmodule.modulestore.django import modulestore from xmodule.modulestore import Location from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.inheritance import own_metadata import json from json.encoder import JSONEncoder import time +import calendar from contentstore.utils import get_modulestore -from util.converters import jsdate_to_time, time_to_date from models.settings import course_grading from contentstore.utils import update_item +from xmodule.fields import Date import re import logging @@ -81,8 +81,14 @@ class CourseDetails(object): dirty = False + # In the descriptor's setter, the date is converted to JSON using Date's to_json method. + # Calling to_json on something that is already JSON doesn't work. Since reaching directly + # into the model is nasty, convert the JSON Date to a Python date, which is what the + # setter expects as input. + date = Date() + if 'start_date' in jsondict: - converted = jsdate_to_time(jsondict['start_date']) + converted = date.from_json(jsondict['start_date']) else: converted = None if converted != descriptor.start: @@ -90,7 +96,7 @@ class CourseDetails(object): descriptor.start = converted if 'end_date' in jsondict: - converted = jsdate_to_time(jsondict['end_date']) + converted = date.from_json(jsondict['end_date']) else: converted = None @@ -99,7 +105,7 @@ class CourseDetails(object): descriptor.end = converted if 'enrollment_start' in jsondict: - converted = jsdate_to_time(jsondict['enrollment_start']) + converted = date.from_json(jsondict['enrollment_start']) else: converted = None @@ -108,7 +114,7 @@ class CourseDetails(object): descriptor.enrollment_start = converted if 'enrollment_end' in jsondict: - converted = jsdate_to_time(jsondict['enrollment_end']) + converted = date.from_json(jsondict['enrollment_end']) else: converted = None @@ -172,12 +178,20 @@ class CourseDetails(object): # TODO move to a more general util? Is there a better way to do the isinstance model check? class CourseSettingsEncoder(json.JSONEncoder): + @staticmethod + def time_to_date(time_obj): + """ + Convert a time.time_struct to a true universal time (can pass to js Date + constructor) + """ + return calendar.timegm(time_obj) * 1000 + def default(self, obj): if isinstance(obj, CourseDetails) or isinstance(obj, course_grading.CourseGradingModel): return obj.__dict__ elif isinstance(obj, Location): return obj.dict() elif isinstance(obj, time.struct_time): - return time_to_date(obj) + return CourseSettingsEncoder.time_to_date(obj) else: return JSONEncoder.default(self, obj) diff --git a/cms/djangoapps/models/settings/course_grading.py b/cms/djangoapps/models/settings/course_grading.py index b20fb71f66..ee9b4ac0eb 100644 --- a/cms/djangoapps/models/settings/course_grading.py +++ b/cms/djangoapps/models/settings/course_grading.py @@ -1,7 +1,5 @@ from xmodule.modulestore import Location from contentstore.utils import get_modulestore -import re -from util import converters from datetime import timedelta diff --git a/common/djangoapps/util/converters.py b/common/djangoapps/util/converters.py deleted file mode 100644 index 212cceb77d..0000000000 --- a/common/djangoapps/util/converters.py +++ /dev/null @@ -1,37 +0,0 @@ -import time -import datetime -import calendar -import dateutil.parser - - -def time_to_date(time_obj): - """ - Convert a time.time_struct to a true universal time (can pass to js Date - constructor) - """ - return calendar.timegm(time_obj) * 1000 - - -def time_to_isodate(source): - '''Convert to an iso date''' - if isinstance(source, time.struct_time): - return time.strftime('%Y-%m-%dT%H:%M:%SZ', source) - elif isinstance(source, datetime): - return source.isoformat() + 'Z' - - -def jsdate_to_time(field): - """ - Convert a universal time (iso format) or msec since epoch to a time obj - """ - if field is None: - return field - elif isinstance(field, basestring): - d = dateutil.parser.parse(field) - return d.utctimetuple() - elif isinstance(field, (int, long, float)): - return time.gmtime(field / 1000) - elif isinstance(field, time.struct_time): - return field - else: - raise ValueError("Couldn't convert %r to time" % field) diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index 0abe850d68..ea857933fc 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -14,7 +14,6 @@ class Date(ModelType): ''' Date fields know how to parse and produce json (iso) compatible formats. ''' - # NB: these are copies of util.converters.* def from_json(self, field): """ Parse an optional metadata key containing a time: if present, complain diff --git a/common/lib/xmodule/xmodule/tests/test_fields.py b/common/lib/xmodule/xmodule/tests/test_fields.py new file mode 100644 index 0000000000..7c8872efc1 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_fields.py @@ -0,0 +1,80 @@ +"""Tests for Date class defined in fields.py.""" +import datetime +import unittest +from django.utils.timezone import UTC +from xmodule.fields import Date +import time + +class DateTest(unittest.TestCase): + date = Date() + + @staticmethod + def struct_to_datetime(struct_time): + return datetime.datetime(struct_time.tm_year, struct_time.tm_mon, + struct_time.tm_mday, struct_time.tm_hour, + struct_time.tm_min, struct_time.tm_sec, tzinfo=UTC()) + + def compare_dates(self, date1, date2, expected_delta): + dt1 = DateTest.struct_to_datetime(date1) + dt2 = DateTest.struct_to_datetime(date2) + self.assertEqual(dt1 - dt2, expected_delta, str(date1) + "-" + + str(date2) + "!=" + str(expected_delta)) + + def test_from_json(self): + '''Test conversion from iso compatible date strings to struct_time''' + self.compare_dates( + DateTest.date.from_json("2013-01-01"), + DateTest.date.from_json("2012-12-31"), + datetime.timedelta(days=1)) + self.compare_dates( + DateTest.date.from_json("2013-01-01T00"), + DateTest.date.from_json("2012-12-31T23"), + datetime.timedelta(hours=1)) + self.compare_dates( + DateTest.date.from_json("2013-01-01T00:00"), + DateTest.date.from_json("2012-12-31T23:59"), + datetime.timedelta(minutes=1)) + self.compare_dates( + DateTest.date.from_json("2013-01-01T00:00:00"), + DateTest.date.from_json("2012-12-31T23:59:59"), + datetime.timedelta(seconds=1)) + self.compare_dates( + DateTest.date.from_json("2013-01-01T00:00:00Z"), + DateTest.date.from_json("2012-12-31T23:59:59Z"), + datetime.timedelta(seconds=1)) + self.compare_dates( + DateTest.date.from_json("2012-12-31T23:00:01-01:00"), + DateTest.date.from_json("2013-01-01T00:00:00+01:00"), + datetime.timedelta(hours=1, seconds=1)) + + def test_return_None(self): + self.assertIsNone(DateTest.date.from_json("")) + self.assertIsNone(DateTest.date.from_json(None)) + self.assertIsNone(DateTest.date.from_json(['unknown value'])) + + def test_old_due_date_format(self): + current = datetime.datetime.today() + self.assertEqual( + time.struct_time((current.year, 3, 12, 12, 0, 0, 1, 71, 0)), + DateTest.date.from_json("March 12 12:00")) + self.assertEqual( + time.struct_time((current.year, 12, 4, 16, 30, 0, 2, 338, 0)), + DateTest.date.from_json("December 4 16:30")) + + def test_to_json(self): + ''' + Test converting time reprs to iso dates + ''' + self.assertEqual( + DateTest.date.to_json( + time.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ")), + "2012-12-31T23:59:59Z") + self.assertEqual( + DateTest.date.to_json( + DateTest.date.from_json("2012-12-31T23:59:59Z")), + "2012-12-31T23:59:59Z") + self.assertEqual( + DateTest.date.to_json( + DateTest.date.from_json("2012-12-31T23:00:01-01:00")), + "2013-01-01T00:00:01Z") + From cddc868656d784da1db5585879c9518918b6a512 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 13:01:10 -0400 Subject: [PATCH 119/483] Login URL resolves differently in LMS and CMS, which breaks login_test when loaded by rake test_cms I moved the test into lms/courseware/tests so they run correctly. --- .../student => lms/djangoapps/courseware}/tests/test_login.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {common/djangoapps/student => lms/djangoapps/courseware}/tests/test_login.py (100%) diff --git a/common/djangoapps/student/tests/test_login.py b/lms/djangoapps/courseware/tests/test_login.py similarity index 100% rename from common/djangoapps/student/tests/test_login.py rename to lms/djangoapps/courseware/tests/test_login.py From ac86687fa104d8c0c96ce3e73b7ad29f7baf5a91 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 14:33:59 -0400 Subject: [PATCH 120/483] Added exception handling that solves SchematicResponse exceptions causing a 500 error. When XModule raises a ProcessingError during an AJAX request, this module_render now returns a 404 to further reduce number of 500 responses. --- common/lib/capa/capa/responsetypes.py | 22 +++++-- common/lib/xmodule/xmodule/capa_module.py | 16 +++-- common/lib/xmodule/xmodule/exceptions.py | 8 ++- .../xmodule/xmodule/tests/test_capa_module.py | 60 ++++++++++++------- lms/djangoapps/courseware/module_render.py | 11 +++- 5 files changed, 86 insertions(+), 31 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index e79399c5fc..bc8e7ff541 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -53,12 +53,17 @@ class LoncapaProblemError(Exception): class ResponseError(Exception): ''' - Error for failure in processing a response + Error for failure in processing a response, including + exceptions that occur when executing a custom script. ''' pass class StudentInputError(Exception): + ''' + Error for an invalid student input. + For example, submitting a string when the problem expects a number + ''' pass #----------------------------------------------------------------------------- @@ -1151,7 +1156,7 @@ def sympy_check2(): # Raise an exception else: log.error(traceback.format_exc()) - raise LoncapaProblemError( + raise ResponseError( "CustomResponse: check function returned an invalid dict") # The check function can return a boolean value, @@ -1226,7 +1231,7 @@ def sympy_check2(): Handle an exception raised during the execution of custom Python code. - Raises a StudentInputError + Raises a ResponseError ''' # Log the error if we are debugging @@ -1236,7 +1241,7 @@ def sympy_check2(): # Notify student with a student input error _, _, traceback_obj = sys.exc_info() - raise StudentInputError, StudentInputError(err.message), traceback_obj + raise ResponseError, ResponseError(err.message), traceback_obj #----------------------------------------------------------------------------- @@ -1912,7 +1917,14 @@ class SchematicResponse(LoncapaResponse): submission = [json.loads(student_answers[ k]) for k in sorted(self.answer_ids)] self.context.update({'submission': submission}) - exec self.code in global_context, self.context + + try: + exec self.code in global_context, self.context + + except Exception as err: + _, _, traceback_obj = sys.exc_info() + raise ResponseError, ResponseError(err.message), traceback_obj + cmap = CorrectMap() cmap.set_dict(dict(zip(sorted( self.answer_ids), self.context['correct']))) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index d7346faa67..fd25016ca5 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -12,12 +12,13 @@ from lxml import etree from pkg_resources import resource_string from capa.capa_problem import LoncapaProblem -from capa.responsetypes import StudentInputError +from capa.responsetypes import StudentInputError, \ + ResponseError, LoncapaProblemError from capa.util import convert_files_to_filenames from .progress import Progress from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor -from xmodule.exceptions import NotFoundError +from xmodule.exceptions import NotFoundError, ProcessingError from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float from .fields import Timedelta @@ -454,7 +455,14 @@ class CapaModule(CapaFields, XModule): return 'Error' before = self.get_progress() - d = handlers[dispatch](get) + + try: + d = handlers[dispatch](get) + + except Exception as err: + _, _, traceback_obj = sys.exc_info() + raise ProcessingError, ProcessingError(err.message), traceback_obj + after = self.get_progress() d.update({ 'progress_changed': after != before, @@ -726,7 +734,7 @@ class CapaModule(CapaFields, XModule): correct_map = self.lcp.grade_answers(answers) self.set_state_from_lcp() - except StudentInputError as inst: + except (StudentInputError, ResponseError, LoncapaProblemError) as inst: log.exception("StudentInputError in capa_module:problem_check") # If the user is a staff member, include diff --git a/common/lib/xmodule/xmodule/exceptions.py b/common/lib/xmodule/xmodule/exceptions.py index 3db5ceccde..d38fbb12bb 100644 --- a/common/lib/xmodule/xmodule/exceptions.py +++ b/common/lib/xmodule/xmodule/exceptions.py @@ -1,6 +1,12 @@ class InvalidDefinitionError(Exception): pass - class NotFoundError(Exception): pass + +class ProcessingError(Exception): + ''' + An error occurred while processing a request to the XModule. + For example: if an exception occurs while checking a capa problem. + ''' + pass diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 18d20a2756..cb7d599413 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -7,6 +7,8 @@ import random import xmodule import capa +from capa.responsetypes import StudentInputError, \ + LoncapaProblemError, ResponseError from xmodule.capa_module import CapaModule from xmodule.modulestore import Location from lxml import etree @@ -502,38 +504,52 @@ class CapaModuleTest(unittest.TestCase): self.assertEqual(module.attempts, 1) - def test_check_problem_student_input_error(self): - module = CapaFactory.create(attempts=1) + def test_check_problem_error(self): - # Ensure that the user is NOT staff - module.system.user_is_staff = False + # Try each exception that capa_module should handle + for exception_class in [StudentInputError, + LoncapaProblemError, + ResponseError]: - # Simulate a student input exception - with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: - mock_grade.side_effect = capa.responsetypes.StudentInputError('test error') + # Create the module + module = CapaFactory.create(attempts=1) - get_request_dict = {CapaFactory.input_key(): '3.14'} - result = module.check_problem(get_request_dict) + # Ensure that the user is NOT staff + module.system.user_is_staff = False + + # Simulate answering a problem that raises the exception + with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: + mock_grade.side_effect = exception_class('test error') + + get_request_dict = {CapaFactory.input_key(): '3.14'} + result = module.check_problem(get_request_dict) # Expect an AJAX alert message in 'success' expected_msg = 'Error: test error' self.assertEqual(expected_msg, result['success']) - # Expect that the number of attempts is NOT incremented - self.assertEqual(module.attempts, 1) + # Expect that the number of attempts is NOT incremented + self.assertEqual(module.attempts, 1) - def test_check_problem_student_input_error_with_staff_user(self): - module = CapaFactory.create(attempts=1) + def test_check_problem_error_with_staff_user(self): + + # Try each exception that capa module should handle + for exception_class in [StudentInputError, + LoncapaProblemError, + ResponseError]: - # Ensure that the user IS staff - module.system.user_is_staff = True + # Create the module + module = CapaFactory.create(attempts=1) - # Simulate a student input exception - with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: - mock_grade.side_effect = capa.responsetypes.StudentInputError('test error') + # Ensure that the user IS staff + module.system.user_is_staff = True - get_request_dict = {CapaFactory.input_key(): '3.14'} - result = module.check_problem(get_request_dict) + # Simulate answering a problem that raises an exception + with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: + mock_grade.side_effect = exception_class('test error') + + get_request_dict = {CapaFactory.input_key(): '3.14'} + result = module.check_problem(get_request_dict) # Expect an AJAX alert message in 'success' self.assertTrue('test error' in result['success']) @@ -541,6 +557,10 @@ class CapaModuleTest(unittest.TestCase): # We DO include traceback information for staff users self.assertTrue('Traceback' in result['success']) + # Expect that the number of attempts is NOT incremented + self.assertEqual(module.attempts, 1) + + def test_reset_problem(self): module = CapaFactory.create(done=True) module.new_lcp = Mock(wraps=module.new_lcp) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 973940d784..182c45775d 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -22,7 +22,7 @@ from .models import StudentModule from psychometrics.psychoanalyze import make_psychometrics_data_update_handler from student.models import unique_id_for_user from xmodule.errortracker import exc_info_to_str -from xmodule.exceptions import NotFoundError +from xmodule.exceptions import NotFoundError, ProcessingError from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from xmodule.x_module import ModuleSystem @@ -443,9 +443,18 @@ def modx_dispatch(request, dispatch, location, course_id): # Let the module handle the AJAX try: ajax_return = instance.handle_ajax(dispatch, p) + + # If we can't find the module, respond with a 404 except NotFoundError: log.exception("Module indicating to user that request doesn't exist") raise Http404 + + # For XModule-specific errors, we respond with 404 + except ProcessingError: + log.exception("Module encountered an error while prcessing AJAX call") + raise Http404 + + # If any other error occurred, re-raise it to trigger a 500 response except: log.exception("error processing ajax call") raise From 99cd3fafdb5f0d2cbe00ad541cb0a07ad83197e5 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 14:48:44 -0400 Subject: [PATCH 121/483] Added error handling of XModule processing errors to CMS Added tests for SchematicResponse error handling --- cms/djangoapps/contentstore/views.py | 8 ++++++- .../lib/capa/capa/tests/test_responsetypes.py | 21 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 561708c833..6ff3e41510 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -42,7 +42,7 @@ from xmodule.modulestore.mongo import MongoUsage from mitxmako.shortcuts import render_to_response, render_to_string from xmodule.modulestore.django import modulestore from xmodule_modifiers import replace_static_urls, wrap_xmodule -from xmodule.exceptions import NotFoundError +from xmodule.exceptions import NotFoundError, ProcessingError from functools import partial from xmodule.contentstore.django import contentstore @@ -448,9 +448,15 @@ def preview_dispatch(request, preview_id, location, dispatch=None): # Let the module handle the AJAX try: ajax_return = instance.handle_ajax(dispatch, request.POST) + except NotFoundError: log.exception("Module indicating to user that request doesn't exist") raise Http404 + + except ProcessingError: + log.exception("Module raised an error while processing AJAX request") + raise Http404 + except: log.exception("error processing ajax call") raise diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index ac50e6defc..d42e9afcb8 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -13,7 +13,8 @@ import textwrap from . import test_system import capa.capa_problem as lcp -from capa.responsetypes import LoncapaProblemError, StudentInputError +from capa.responsetypes import LoncapaProblemError, \ + StudentInputError, ResponseError from capa.correctmap import CorrectMap from capa.util import convert_files_to_filenames from capa.xqueue_interface import dateformat @@ -865,7 +866,7 @@ class CustomResponseTest(ResponseTest): problem = self.build_problem(script=script, cfn="check_func") # Expect that an exception gets raised when we check the answer - with self.assertRaises(StudentInputError): + with self.assertRaises(ResponseError): problem.grade_answers({'1_2_1': '42'}) def test_script_exception_inline(self): @@ -875,7 +876,7 @@ class CustomResponseTest(ResponseTest): problem = self.build_problem(answer=script) # Expect that an exception gets raised when we check the answer - with self.assertRaises(StudentInputError): + with self.assertRaises(ResponseError): problem.grade_answers({'1_2_1': '42'}) def test_invalid_dict_exception(self): @@ -889,7 +890,7 @@ class CustomResponseTest(ResponseTest): problem = self.build_problem(script=script, cfn="check_func") # Expect that an exception gets raised when we check the answer - with self.assertRaises(LoncapaProblemError): + with self.assertRaises(ResponseError): problem.grade_answers({'1_2_1': '42'}) @@ -922,6 +923,18 @@ class SchematicResponseTest(ResponseTest): # is what we expect) self.assertEqual(correct_map.get_correctness('1_2_1'), 'correct') + def test_script_exception(self): + + # Construct a script that will raise an exception + script = "raise Exception('test')" + problem = self.build_problem(answer=script) + + # Expect that an exception gets raised when we check the answer + with self.assertRaises(ResponseError): + submission_dict = {'test': 'test'} + input_dict = {'1_2_1': json.dumps(submission_dict)} + problem.grade_answers(input_dict) + class AnnotationResponseTest(ResponseTest): from response_xml_factory import AnnotationResponseXMLFactory From 22537ffd3b05269b688972e7e2ad81e118cc1da7 Mon Sep 17 00:00:00 2001 From: cahrens Date: Wed, 27 Mar 2013 14:51:39 -0400 Subject: [PATCH 122/483] Don't need to convert to milliseconds. --- cms/djangoapps/models/settings/course_details.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/cms/djangoapps/models/settings/course_details.py b/cms/djangoapps/models/settings/course_details.py index 09d57774ab..b45f5bd343 100644 --- a/cms/djangoapps/models/settings/course_details.py +++ b/cms/djangoapps/models/settings/course_details.py @@ -178,20 +178,12 @@ class CourseDetails(object): # TODO move to a more general util? Is there a better way to do the isinstance model check? class CourseSettingsEncoder(json.JSONEncoder): - @staticmethod - def time_to_date(time_obj): - """ - Convert a time.time_struct to a true universal time (can pass to js Date - constructor) - """ - return calendar.timegm(time_obj) * 1000 - def default(self, obj): if isinstance(obj, CourseDetails) or isinstance(obj, course_grading.CourseGradingModel): return obj.__dict__ elif isinstance(obj, Location): return obj.dict() elif isinstance(obj, time.struct_time): - return CourseSettingsEncoder.time_to_date(obj) + return Date().to_json(obj) else: return JSONEncoder.default(self, obj) From 5c78218b1360bf9e0eb6bcee41cbc44e1aeb1dac Mon Sep 17 00:00:00 2001 From: cahrens Date: Wed, 27 Mar 2013 14:52:27 -0400 Subject: [PATCH 123/483] Don't need to convert to milliseconds. --- cms/djangoapps/models/settings/course_details.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cms/djangoapps/models/settings/course_details.py b/cms/djangoapps/models/settings/course_details.py index b45f5bd343..876000c7fe 100644 --- a/cms/djangoapps/models/settings/course_details.py +++ b/cms/djangoapps/models/settings/course_details.py @@ -4,7 +4,6 @@ from xmodule.modulestore.inheritance import own_metadata import json from json.encoder import JSONEncoder import time -import calendar from contentstore.utils import get_modulestore from models.settings import course_grading from contentstore.utils import update_item From 122c8567c5d370a6e54e075d4e736c96bcfef646 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 27 Mar 2013 15:00:08 -0400 Subject: [PATCH 124/483] An integrity error while creating an enrollment just means that our work has already been done. Fixes https://www.pivotaltracker.com/story/show/46915947 --- common/djangoapps/student/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 5dbaf5d2c2..d0deffd7b9 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -325,7 +325,12 @@ def change_enrollment(request): "course:{0}".format(course_num), "run:{0}".format(run)]) - enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) + try: + enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) + except IntegrityError: + # If we've already created this enrollment in a separate transaction, + # then just continue + pass return {'success': True} elif action == "unenroll": From df1be877390c6869b766870c7d5e40bbfe258913 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 15:20:40 -0400 Subject: [PATCH 125/483] * Changed 404 errors to 400 errors * Removed duplicate traceback log message * Now provide string, not Exception, as second tuple item to raise --- cms/djangoapps/contentstore/views.py | 2 +- common/lib/capa/capa/responsetypes.py | 3 +-- common/lib/xmodule/xmodule/capa_module.py | 2 +- lms/djangoapps/courseware/module_render.py | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 6ff3e41510..24f3eae8a4 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -455,7 +455,7 @@ def preview_dispatch(request, preview_id, location, dispatch=None): except ProcessingError: log.exception("Module raised an error while processing AJAX request") - raise Http404 + return HttpResponseBadRequest() except: log.exception("error processing ajax call") diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index bc8e7ff541..3d19fb4cb1 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1237,11 +1237,10 @@ def sympy_check2(): # Log the error if we are debugging msg = 'Error occurred while evaluating CustomResponse' log.debug(msg, exc_info=True) - log.debug(traceback.format_exc()) # Notify student with a student input error _, _, traceback_obj = sys.exc_info() - raise ResponseError, ResponseError(err.message), traceback_obj + raise ResponseError, err.message, traceback_obj #----------------------------------------------------------------------------- diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index fd25016ca5..4975478421 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -461,7 +461,7 @@ class CapaModule(CapaFields, XModule): except Exception as err: _, _, traceback_obj = sys.exc_info() - raise ProcessingError, ProcessingError(err.message), traceback_obj + raise ProcessingError, err.message, traceback_obj after = self.get_progress() d.update({ diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 182c45775d..39d16dbb19 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -10,7 +10,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.http import Http404 -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseBadRequest from django.views.decorators.csrf import csrf_exempt from requests.auth import HTTPBasicAuth @@ -449,10 +449,10 @@ def modx_dispatch(request, dispatch, location, course_id): log.exception("Module indicating to user that request doesn't exist") raise Http404 - # For XModule-specific errors, we respond with 404 + # For XModule-specific errors, we respond with 400 except ProcessingError: log.exception("Module encountered an error while prcessing AJAX call") - raise Http404 + return HttpResponseBadRequest() # If any other error occurred, re-raise it to trigger a 500 response except: From 756f75951dca8311ff0d642af2ef3abddbf2c9b3 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 16:28:49 -0400 Subject: [PATCH 126/483] studio - in progress work on help UI --- cms/static/client_templates/checklist.html | 2 +- cms/static/sass/_base.scss | 3 +- cms/static/sass/_variables.scss | 2 +- cms/static/sass/base-style.scss | 2 + cms/static/sass/elements/_sock.scss | 95 +++++++++++++++++++++- cms/static/sass/views/_account.scss | 2 +- cms/templates/howitworks.html | 4 +- cms/templates/widgets/footer.html | 5 +- cms/templates/widgets/sock.html | 41 +++++++++- 9 files changed, 143 insertions(+), 13 deletions(-) diff --git a/cms/static/client_templates/checklist.html b/cms/static/client_templates/checklist.html index ec6ff4e892..6884b0e9c9 100644 --- a/cms/static/client_templates/checklist.html +++ b/cms/static/client_templates/checklist.html @@ -44,7 +44,7 @@ <% if (item['action_text'] !== '' && item['action_url'] !== '') { %>
      -
    • +
    • rel="external" title="This link will open in a new browser window/tab" diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 5901b19306..5ce131288e 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -357,7 +357,8 @@ h1 { // layout - grandfathered .main-wrapper { position: relative; - margin: 40px; + margin: ($baseline*2); + padding-bottom: $footer-primary-height; } .inner-wrapper { diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index ccbd3ed7b0..ffa99e3fc6 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -155,7 +155,7 @@ $shadow-l1: rgba(0,0,0,0.1); $shadow-d1: rgba(0,0,0,0.4); // colors - inherited -$baseFontColor: #3c3c3c; +$baseFontColor: $gray-d2; $offBlack: #3c3c3c; $green: #108614; $lightGrey: #edf1f5; diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index bc6638bf14..e1afa45804 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -21,6 +21,8 @@ @import 'base'; // elements +@import 'elements/typography'; +@import 'elements/icons'; @import 'elements/header'; @import 'elements/sock'; @import 'elements/footer'; diff --git a/cms/static/sass/elements/_sock.scss b/cms/static/sass/elements/_sock.scss index e1b147b849..e8fbcc3ef2 100644 --- a/cms/static/sass/elements/_sock.scss +++ b/cms/static/sass/elements/_sock.scss @@ -9,10 +9,101 @@ .sock { @include clearfix(); - @include font-size(13); + @extend .t-copy-sub2; max-width: $fg-max-width; min-width: $fg-min-width; width: flex-grid(12); - margin: 0 auto ($baseline*2) auto; + margin: 0 auto $baseline auto; + + header { + + .title { + @extend .t-title-3; + } + + .ss-icon { + @extend .t-icon-inline; + } + } + + // shared elements + .support, .feedback { + @include box-sizing(border-box); + + .title { + + } + + .copy { + margin: 0 0 $baseline 0; + } + + .list-actions { + @include clearfix(); + + .action-item { + float: left; + margin-right: ($baseline/2); + + &:last-child { + margin-right: 0; + } + + .action { + display: block; + + .ss-icon { + @include transition(color .25s ease-in-out); + @include font-size(15); + @extend .t-icon-inline; + @extend .icon-inline; + margin-right: ($baseline/4); + color: $blue-l2; + } + + &:hover, &:active { + + .ss-icon { + color: $white; + } + } + } + + .tip { + @extend .sr; + } + } + + .action-primary { + @include blue-button; + @include transition(all .15s); + @include font-size(13); + font-weight: 500; + padding: ($baseline/4) ($baseline/2); + text-align: center; + } + } + } + + // studio support content + .support { + width: flex-grid(8,12); + float: left; + margin-right: flex-gutter(); + + .action-item { + width: flexgrid(4,8); + } + } + + // studio feedback content + .feedback { + width: flex-grid(4,12); + float: left; + + .action-item { + width: flexgrid(4,4); + } + } } } \ No newline at end of file diff --git a/cms/static/sass/views/_account.scss b/cms/static/sass/views/_account.scss index 1206db5e76..2be94a81ea 100644 --- a/cms/static/sass/views/_account.scss +++ b/cms/static/sass/views/_account.scss @@ -71,7 +71,7 @@ body.signup, body.signin { @include blue-button; @include transition(all .15s); @include font-size(15); - display:block; + display: block; width: 100%; padding: ($baseline*0.75) ($baseline/2); font-weight: 600; diff --git a/cms/templates/howitworks.html b/cms/templates/howitworks.html index 7a819fceba..6c0029c425 100644 --- a/cms/templates/howitworks.html +++ b/cms/templates/howitworks.html @@ -134,10 +134,10 @@ diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index 18ecf2bc39..c00bf0187a 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -20,12 +20,9 @@
    • % if user.is_authenticated(): % endif - diff --git a/cms/templates/widgets/sock.html b/cms/templates/widgets/sock.html index ff5f9c9ad4..d8d191a773 100644 --- a/cms/templates/widgets/sock.html +++ b/cms/templates/widgets/sock.html @@ -2,7 +2,46 @@ % if user.is_authenticated():
      -

      Sock!

      +
      +

      Studio Support

      +
      + +
      +

      Studio Support

      + +
      +

      Need help getting started with Studio? Want to know how to manage a particular part of your course using Studio? Take advantage of our documentation, help forums, as well as our edX101 introduction course for course authors.

      +
      + + +
      + +
      % endif \ No newline at end of file From 6564cc57e6f7a2bddfab8a9dabbcc012687135a1 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 27 Mar 2013 16:29:55 -0400 Subject: [PATCH 127/483] Fix typo with hyphen in cms lettuce feature files --- .../contentstore/features/advanced-settings.feature | 6 +++--- .../features/studio-overview-togglesection.feature | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.feature b/cms/djangoapps/contentstore/features/advanced-settings.feature index 66039e19b1..db7294c14c 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.feature +++ b/cms/djangoapps/contentstore/features/advanced-settings.feature @@ -27,16 +27,16 @@ Feature: Advanced (manual) course policy And I reload the page Then the policy key value is changed - Scenario: Test how multi -line input appears + Scenario: Test how multi-line input appears Given I am on the Advanced Course Settings page in Studio When I create a JSON object as a value Then it is displayed as formatted And I reload the page Then it is displayed as formatted - Scenario: Test automatic quoting of non -JSON values + Scenario: Test automatic quoting of non-JSON values Given I am on the Advanced Course Settings page in Studio - When I create a non -JSON value not in quotes + When I create a non-JSON value not in quotes Then it is displayed as a string And I reload the page Then it is displayed as a string diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature index 88492d55e3..762dea6838 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature @@ -21,7 +21,7 @@ Feature: Overview Toggle Section Then I see the "Collapse All Sections" link And all sections are expanded - @skip -phantom + @skip-phantom Scenario: Collapse link is not removed after last section of a course is deleted Given I have a course with 1 section And I navigate to the course overview page From f038237ee9f2d7a5dae9c2ebdb6a2ba57db860c8 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 16:34:08 -0400 Subject: [PATCH 128/483] Changed log.exception to log.warning --- cms/djangoapps/contentstore/views.py | 2 +- common/lib/capa/capa/responsetypes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 24f3eae8a4..bfdfb1742b 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -454,7 +454,7 @@ def preview_dispatch(request, preview_id, location, dispatch=None): raise Http404 except ProcessingError: - log.exception("Module raised an error while processing AJAX request") + log.warning("Module raised an error while processing AJAX request") return HttpResponseBadRequest() except: diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 3d19fb4cb1..bc62654bef 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1236,7 +1236,7 @@ def sympy_check2(): # Log the error if we are debugging msg = 'Error occurred while evaluating CustomResponse' - log.debug(msg, exc_info=True) + log.warning(msg, exc_info=True) # Notify student with a student input error _, _, traceback_obj = sys.exc_info() From 9c671163fdf1be224cf4d3f310380fa9caa75cf8 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 27 Mar 2013 17:11:02 -0400 Subject: [PATCH 129/483] Added exc_info=True to log.warning Changed log.exception to log.warning --- cms/djangoapps/contentstore/views.py | 3 ++- common/lib/xmodule/xmodule/capa_module.py | 3 ++- lms/djangoapps/courseware/module_render.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index bfdfb1742b..0d58133763 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -454,7 +454,8 @@ def preview_dispatch(request, preview_id, location, dispatch=None): raise Http404 except ProcessingError: - log.warning("Module raised an error while processing AJAX request") + log.warning("Module raised an error while processing AJAX request", + exc_info=True) return HttpResponseBadRequest() except: diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 4975478421..3e594f9d46 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -735,7 +735,8 @@ class CapaModule(CapaFields, XModule): self.set_state_from_lcp() except (StudentInputError, ResponseError, LoncapaProblemError) as inst: - log.exception("StudentInputError in capa_module:problem_check") + log.warning("StudentInputError in capa_module:problem_check", + exc_info=True) # If the user is a staff member, include # the full exception, including traceback, diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 39d16dbb19..48aab024df 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -451,7 +451,8 @@ def modx_dispatch(request, dispatch, location, course_id): # For XModule-specific errors, we respond with 400 except ProcessingError: - log.exception("Module encountered an error while prcessing AJAX call") + log.warning("Module encountered an error while prcessing AJAX call", + exc_info=True) return HttpResponseBadRequest() # If any other error occurred, re-raise it to trigger a 500 response From c8ab45cc579c675265b0d9223d61eb2c3310614c Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 27 Mar 2013 17:40:24 -0400 Subject: [PATCH 130/483] studio - footer help ui revamp - animation, structure, styling - WIP --- cms/static/js/base.js | 11 +- cms/static/sass/_base.scss | 8 ++ cms/static/sass/_cms_mixins.scss | 15 +++ cms/static/sass/_variables.scss | 2 +- cms/static/sass/base-style.scss | 1 - cms/static/sass/elements/_footer.scss | 121 ++++++++++++++++++++++ cms/static/sass/elements/_icons.scss | 16 +++ cms/static/sass/elements/_sock.scss | 109 ------------------- cms/static/sass/elements/_typography.scss | 82 +++++++++++++++ cms/templates/base.html | 5 +- cms/templates/widgets/footer.html | 45 +++++++- cms/templates/widgets/sock.html | 47 --------- 12 files changed, 299 insertions(+), 163 deletions(-) create mode 100644 cms/static/sass/elements/_icons.scss delete mode 100644 cms/static/sass/elements/_sock.scss create mode 100644 cms/static/sass/elements/_typography.scss delete mode 100644 cms/templates/widgets/sock.html diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 211981b05a..d93d2fd8d4 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -34,11 +34,11 @@ $(document).ready(function () { $(this).select(); }); + $('body').addClass('js'); + $('.unit .item-actions .delete-button').bind('click', deleteUnit); $('.new-unit-item').bind('click', createNewUnit); - $('body').addClass('js'); - // lean/simple modal $('a[rel*=modal]').leanModal({overlay : 0.80, closeButton: '.action-modal-close' }); $('a.action-modal-close').click(function(e){ @@ -86,6 +86,8 @@ $(document).ready(function () { // tender feedback window scrolling $('a.show-tender').bind('click', smoothScrollTop); + // toggling footer additional support + $('.show-support').bind('click', toggleSupport); // toggling overview section details $(function () { @@ -456,6 +458,11 @@ function onKeyUp(e) { } } +function toggleSupport(e) { + e.preventDefault(); + $body.toggleClass('footer-is-expanded'); +} + function toggleSubmodules(e) { e.preventDefault(); $(this).toggleClass('expand').toggleClass('collapse'); diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 5ce131288e..328c7e99c3 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -56,6 +56,14 @@ h1 { min-height: 100%; } +.wrapper-main { + padding-bottom: $footer-primary-height; +} + +.js.footer-is-expanded .wrapper-main { + padding-bottom: ($footer-primary-height*4); +} + // layout - basic page header .wrapper-mast { margin: 0; diff --git a/cms/static/sass/_cms_mixins.scss b/cms/static/sass/_cms_mixins.scss index 015a94b762..d837000a24 100644 --- a/cms/static/sass/_cms_mixins.scss +++ b/cms/static/sass/_cms_mixins.scss @@ -110,6 +110,21 @@ } } +@mixin gray-button { + @include button; + border: 1px solid $gray-d1; + border-radius: 3px; + @include linear-gradient(top, $white-t1, rgba(255, 255, 255, 0)); + background-color: $gray-d2; + @include box-shadow(0 1px 0 $white-t1 inset); + color: $gray-l3; + + &:hover { + background-color: $gray-d3; + color: $white; + } +} + @mixin green-button { @include button; border: 1px solid $darkGreen; diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index ffa99e3fc6..806fbc8c57 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -13,7 +13,7 @@ $fg-max-width: 1280px; $fg-min-width: 900px; // elements -$footer-primary-height: ($baseline*3); +$footer-primary-height: (60px); // type $sans-serif: 'Open Sans', $verdana; diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index e1afa45804..3015c3592d 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -24,7 +24,6 @@ @import 'elements/typography'; @import 'elements/icons'; @import 'elements/header'; -@import 'elements/sock'; @import 'elements/footer'; @import 'elements/navigation'; @import 'elements/forms'; diff --git a/cms/static/sass/elements/_footer.scss b/cms/static/sass/elements/_footer.scss index dfebc6d44c..f2941e11be 100644 --- a/cms/static/sass/elements/_footer.scss +++ b/cms/static/sass/elements/_footer.scss @@ -75,4 +75,125 @@ } } } + + // sock - additional help + .sock { + @include clearfix(); + @extend .t-copy-sub2; + max-width: $fg-max-width; + min-width: $fg-min-width; + width: flex-grid(12); + margin: 0 auto $baseline auto; + border-bottom: 1px solid $gray-l2; + padding-bottom: $baseline; + + header { + + .title { + @extend .t-title-3; + } + + .ss-icon { + @extend .t-icon; + @extend .icon-inline; + } + } + + // shared elements + .support, .feedback { + @include box-sizing(border-box); + + .title { + + } + + .copy { + margin: 0 0 $baseline 0; + } + + .list-actions { + @include clearfix(); + + .action-item { + float: left; + margin-right: ($baseline/2); + + &:last-child { + margin-right: 0; + } + + .action { + display: block; + + .ss-icon { + @include font-size(15); + @extend .t-icon; + @extend .icon-inline; + } + + &:hover, &:active { + + .ss-icon { + } + } + } + + .tip { + @extend .sr; + } + } + + .action-primary { + @include gray-button; + @include transition(all .15s); + @include font-size(13); + font-weight: 500; + padding: ($baseline/4) ($baseline/2); + text-align: center; + } + } + } + + // studio support content + .support { + width: flex-grid(8,12); + float: left; + margin-right: flex-gutter(); + + .action-item { + width: flexgrid(4,8); + } + } + + // studio feedback content + .feedback { + width: flex-grid(4,12); + float: left; + + .action-item { + width: flexgrid(4,4); + } + } + } +} + + +// js-enabled styling +.js .wrapper-footer { + @include transition(height 2s ease-in-out); + height: $footer-primary-height; + overflow: hidden; + + .sock { + display: none; + } +} + + // expanded view +.js.footer-is-expanded .wrapper-footer { + height: ($footer-primary-height*4); + + .sock { + display: block; + } } \ No newline at end of file diff --git a/cms/static/sass/elements/_icons.scss b/cms/static/sass/elements/_icons.scss new file mode 100644 index 0000000000..2bc73d8b8d --- /dev/null +++ b/cms/static/sass/elements/_icons.scss @@ -0,0 +1,16 @@ +// studio - elements - icons +// ==================== + +.icon { + +} + +.ss-icon { + +} + +.icon-inline { + display: inline-block; + vertical-align: middle; + margin-right: ($baseline/4); +} \ No newline at end of file diff --git a/cms/static/sass/elements/_sock.scss b/cms/static/sass/elements/_sock.scss deleted file mode 100644 index e8fbcc3ef2..0000000000 --- a/cms/static/sass/elements/_sock.scss +++ /dev/null @@ -1,109 +0,0 @@ -// studio - elements - sock -// ==================== - -.wrapper-sock { - margin: 0; - padding: $baseline $baseline $footer-primary-height $baseline; - position: relative; - width: 100%; - - .sock { - @include clearfix(); - @extend .t-copy-sub2; - max-width: $fg-max-width; - min-width: $fg-min-width; - width: flex-grid(12); - margin: 0 auto $baseline auto; - - header { - - .title { - @extend .t-title-3; - } - - .ss-icon { - @extend .t-icon-inline; - } - } - - // shared elements - .support, .feedback { - @include box-sizing(border-box); - - .title { - - } - - .copy { - margin: 0 0 $baseline 0; - } - - .list-actions { - @include clearfix(); - - .action-item { - float: left; - margin-right: ($baseline/2); - - &:last-child { - margin-right: 0; - } - - .action { - display: block; - - .ss-icon { - @include transition(color .25s ease-in-out); - @include font-size(15); - @extend .t-icon-inline; - @extend .icon-inline; - margin-right: ($baseline/4); - color: $blue-l2; - } - - &:hover, &:active { - - .ss-icon { - color: $white; - } - } - } - - .tip { - @extend .sr; - } - } - - .action-primary { - @include blue-button; - @include transition(all .15s); - @include font-size(13); - font-weight: 500; - padding: ($baseline/4) ($baseline/2); - text-align: center; - } - } - } - - // studio support content - .support { - width: flex-grid(8,12); - float: left; - margin-right: flex-gutter(); - - .action-item { - width: flexgrid(4,8); - } - } - - // studio feedback content - .feedback { - width: flex-grid(4,12); - float: left; - - .action-item { - width: flexgrid(4,4); - } - } - } -} \ No newline at end of file diff --git a/cms/static/sass/elements/_typography.scss b/cms/static/sass/elements/_typography.scss new file mode 100644 index 0000000000..a9b3d362ee --- /dev/null +++ b/cms/static/sass/elements/_typography.scss @@ -0,0 +1,82 @@ +// studio - elements - typography +// ==================== + +// headings/titles +.t-title-1, .t-title-2, .t-title-3, .t-title-4, .t-title-5, .t-title-5 { + color: $gray-d3; +} + +.t-title-1 { + @include font-size(32); +} + +.t-title-2 { + @include font-size(24); + margin: 0 0 ($baseline/2) 0; + font-weight: 600; +} + +.t-title-3 { + @include font-size(16); + margin: 0 0 ($baseline/2) 0; + font-weight: 600; +} + +.t-title-4 { + +} + +.t-title-5 { + +} + +// ==================== + +// copy +.t-copy-base { + @include font-size(16); +} + +.t-copy-lead1 { + @include font-size(18); +} + +.t-copy-lead2 { + @include font-size(20); +} + +.t-copy-sub1 { + @include font-size(14); +} + +.t-copy-sub2 { + @include font-size(13); +} + +.t-copy-sub3 { + @include font-size(12); +} + +// ==================== + +// actions/labels +.t-action { + @include font-size(14); + font-weight: 600; +} + +.t-action-primary { + @include font-size(14); + font-weight: 600; +} + +.t-action-primary-s { + @include font-size(13); +} + +// ==================== + +// misc +.t-icon { + line-height: 0; +} \ No newline at end of file diff --git a/cms/templates/base.html b/cms/templates/base.html index 44847c1da4..e587619bae 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -53,8 +53,9 @@
      <%include file="widgets/header.html" /> - <%block name="content"> - <%include file="widgets/sock.html" /> +
      + <%block name="content"> +
      <%include file="widgets/footer.html" />
      diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index c00bf0187a..b6a3b84272 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -1,6 +1,49 @@ <%! from django.core.urlresolvers import reverse %>
    +
    %endif @@ -202,6 +203,8 @@ function goto( mode) %if instructor_access:

    You may also delete the entire state of a student for a problem:

    +

    To delete the state of other XBlocks specify modulename/urlname, eg + combinedopenended/Humanities_SA_Peer

    %endif %endif From 57e5eb683bbb745c911d6988a3f08ac3a7e08bc2 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 1 Apr 2013 16:54:56 -0400 Subject: [PATCH 224/483] studio - basic design, interaction, and content for static in-page help --- cms/static/js/base.js | 14 +- cms/static/sass/_base.scss | 27 ++-- cms/static/sass/_cms_mixins.scss | 103 +++++++++++++-- cms/static/sass/_variables.scss | 4 +- cms/static/sass/base-style.scss | 4 +- cms/static/sass/elements/_controls.scss | 143 ++++++++++++++++++++ cms/static/sass/elements/_footer.scss | 133 +------------------ cms/static/sass/elements/_header.scss | 2 +- cms/static/sass/elements/_sock.scss | 152 ++++++++++++++++++++++ cms/static/sass/elements/_typography.scss | 23 ++-- cms/static/sass/views/_index.scss | 3 +- cms/templates/base.html | 11 +- cms/templates/widgets/footer.html | 52 +------- cms/templates/widgets/sock.html | 52 ++++++++ 14 files changed, 499 insertions(+), 224 deletions(-) create mode 100644 cms/static/sass/elements/_controls.scss create mode 100644 cms/static/sass/elements/_sock.scss create mode 100644 cms/templates/widgets/sock.html diff --git a/cms/static/js/base.js b/cms/static/js/base.js index d93d2fd8d4..7bea860531 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -87,7 +87,7 @@ $(document).ready(function () { $('a.show-tender').bind('click', smoothScrollTop); // toggling footer additional support - $('.show-support').bind('click', toggleSupport); + $('.cta-show-sock').bind('click', toggleSock); // toggling overview section details $(function () { @@ -458,9 +458,17 @@ function onKeyUp(e) { } } -function toggleSupport(e) { +function toggleSock(e) { e.preventDefault(); - $body.toggleClass('footer-is-expanded'); + $body.toggleClass('sock-is-shown'); + + $.smoothScroll({ + offset: -200, + easing: 'swing', + speed: 1000, + scrollElement: null, + scrollTarget: $('.wrapper-sock') + }); } function toggleSubmodules(e) { diff --git a/cms/static/sass/_base.scss b/cms/static/sass/_base.scss index 328c7e99c3..10c046d22a 100644 --- a/cms/static/sass/_base.scss +++ b/cms/static/sass/_base.scss @@ -22,7 +22,7 @@ body, input { a { text-decoration: none; color: $blue; - @include transition(color .15s); + @include transition(color 0.25s ease-in-out); &:hover { color: #cb9c40; @@ -50,18 +50,22 @@ h1 { // ==================== -// layout - view +// layout - overall view .wrapper-view { - position: relative; min-height: 100%; + position: relative; } .wrapper-main { - padding-bottom: $footer-primary-height; + background: $gray-l5; + margin: ($baseline*1.5) 0 0 0; + padding-bottom: $bottom-height; } -.js.footer-is-expanded .wrapper-main { - padding-bottom: ($footer-primary-height*4); +.wrapper-bottom { + position: absolute; + bottom: 0; + height: $bottom-height; } // layout - basic page header @@ -286,19 +290,17 @@ h1 { } .title-1 { - + @extend .t-title-1; } .title-2 { - @include font-size(24); + @extend .t-title-2; margin: 0 0 ($baseline/2) 0; - font-weight: 600; } .title-3 { - @include font-size(16); + @extend .t-title-3; margin: 0 0 ($baseline/2) 0; - font-weight: 600; } .title-4 { @@ -365,8 +367,7 @@ h1 { // layout - grandfathered .main-wrapper { position: relative; - margin: ($baseline*2); - padding-bottom: $footer-primary-height; + margin: 0 ($baseline*2); } .inner-wrapper { diff --git a/cms/static/sass/_cms_mixins.scss b/cms/static/sass/_cms_mixins.scss index d837000a24..a25a07cb73 100644 --- a/cms/static/sass/_cms_mixins.scss +++ b/cms/static/sass/_cms_mixins.scss @@ -1,6 +1,7 @@ // studio - utilities - mixins and extends // ==================== +// mixins - utility @mixin clearfix { &:after { content: ''; @@ -11,6 +12,7 @@ } } +// mixins - grandfathered @mixin button { display: inline-block; padding: 4px 20px 6px; @@ -294,20 +296,97 @@ } } -@mixin sr-text { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} - @mixin active { @include linear-gradient(top, rgba(255, 255, 255, .4), rgba(255, 255, 255, 0)); background-color: rgba(255, 255, 255, .3); @include box-shadow(0 -1px 0 rgba(0, 0, 0, .2) inset, 0 1px 0 #fff inset); text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} \ No newline at end of file +} + +// ==================== + +// extends - buttons +.btn { + @include box-sizing(border-box); + @include transition(color 0.25s ease-in-out, border-color 0.25s ease-in-out, background 0.25s ease-in-out, box-shadow 0.25s ease-in-out); + display: inline-block; + cursor: pointer; + + &:hover, &:active { + + } + + &.disabled, &[disabled] { + cursor: default; + pointer-events: none; + opacity: 0.5; + } + + .icon-inline { + display: inline-block; + vertical-align: middle; + margin-right: ($baseline/4); + } +} + +// pill button +.btn-pill { + @include border-radius($baseline/5); +} + +.btn-rounded { + @include border-radius($baseline/2); +} + +// primary button +.btn-primary { + @extend .btn; + @extend .btn-pill; + padding:($baseline/2) $baseline; + border-width: 1px; + border-style: solid; + line-height: 1.5em; + text-align: center; + + &:hover, &:active { + @include box-shadow(0 2px 1px $shadow-l1); + } + + &.current, &.active { + @include box-shadow(inset 1px 1px 2px $shadow-d1); + + &:hover, &:active { + @include box-shadow(inset 1px 1px 1px $shadow-d1); + } + } +} + +// secondary button +.btn-secondary { + @extend .btn; + @extend .btn-pill; + border-width: 1px; + border-style: solid; + padding:($baseline/2) $baseline; + background: transparent; + line-height: 1.5em; + text-align: center; + + &:hover, &:active { + + } + + &.current, &.active { + + } +} + +// ==================== + +// extends - depth levels +.depth0 { z-index: 0; } +.depth1 { z-index: 10; } +.depth2 { z-index: 100; } +.depth3 { z-index: 1000; } +.depth4 { z-index: 10000; } +.depth5 { z-index: 100000; } \ No newline at end of file diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/_variables.scss index 806fbc8c57..88143c2365 100644 --- a/cms/static/sass/_variables.scss +++ b/cms/static/sass/_variables.scss @@ -13,7 +13,7 @@ $fg-max-width: 1280px; $fg-min-width: 900px; // elements -$footer-primary-height: (60px); +$bottom-height: ($baseline*3); // type $sans-serif: 'Open Sans', $verdana; @@ -170,4 +170,4 @@ $disabledGreen: rgb(124, 206, 153); $darkGreen: rgb(52, 133, 76); $lightBluishGrey: rgb(197, 207, 223); $lightBluishGrey2: rgb(213, 220, 228); -$error-red: rgb(253, 87, 87); +$error-red: rgb(253, 87, 87); \ No newline at end of file diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index 3015c3592d..da17441c71 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -23,9 +23,11 @@ // elements @import 'elements/typography'; @import 'elements/icons'; +@import 'elements/controls'; +@import 'elements/navigation'; @import 'elements/header'; @import 'elements/footer'; -@import 'elements/navigation'; +@import 'elements/sock'; @import 'elements/forms'; @import 'elements/modal'; @import 'elements/alerts'; diff --git a/cms/static/sass/elements/_controls.scss b/cms/static/sass/elements/_controls.scss new file mode 100644 index 0000000000..c4e96616a8 --- /dev/null +++ b/cms/static/sass/elements/_controls.scss @@ -0,0 +1,143 @@ +// studio - elements - UI controls +// ==================== + +// gray primary button +.btn-primary-gray { + @extend .btn-primary; + background: $gray-l1; + border-color: $gray-l2; + color: $white; + + &:hover, &:active { + border-color: $gray-l1; + background: $gray; + } + + &.current, &.active { + background: $gray-d1; + color: $gray-l1; + + &:hover, &:active { + background: $gray-d1; + } + } +} + +// blue primary button +.btn-primary-blue { + @extend .btn-primary; + background: $blue; + border-color: $blue-s1; + color: $white; + + &:hover, &:active { + background: $blue-s2; + border-color: $blue-s2; + } + + &.current, &.active { + background: $blue-d1; + color: $blue-l4; + border-color: $blue-d2; + + &:hover, &:active { + background: $blue-d1; + } + } +} + +// green primary button +.btn-primary-green { + @extend .btn-primary; + background: $green; + border-color: $green; + color: $white; + + &:hover, &:active { + background: $green-s1; + border-color: $green-s1; + } + + &.current, &.active { + background: $green-d1; + color: $green-l4; + border-color: $green-d2; + + &:hover, &:active { + background: $green-d1; + } + } +} + +// gray secondary button +.btn-secondary-gray { + @extend .btn-secondary; + border-color: $gray-l3; + color: $gray-l1; + + &:hover, &:active { + background: $gray-l3; + color: $gray-d2; + } + + &.current, &.active { + background: $gray-d2; + color: $gray-l5; + + &:hover, &:active { + background: $gray-d2; + } + } +} + +// blue secondary button +.btn-secondary-blue { + @extend .btn-secondary; + border-color: $blue-l3; + color: $blue; + + &:hover, &:active { + background: $blue-l3; + color: $blue-s2; + } + + &.current, &.active { + border-color: $blue-l3; + background: $blue-l3; + color: $blue-d1; + + &:hover, &:active { + + } + } +} + +// green secondary button +.btn-secondary-green { + @extend .btn-secondary; + border-color: $green-l4; + color: $green-l2; + + &:hover, &:active { + background: $green-l4; + color: $green-s1; + } + + &.current, &.active { + background: $green-s1; + color: $green-l4; + + &:hover, &:active { + background: $green-s1; + } + } +} + +// ==================== + +// layout-based buttons + +// ==================== + +// calls-to-action + diff --git a/cms/static/sass/elements/_footer.scss b/cms/static/sass/elements/_footer.scss index f2941e11be..d0bb9b9dee 100644 --- a/cms/static/sass/elements/_footer.scss +++ b/cms/static/sass/elements/_footer.scss @@ -3,13 +3,10 @@ .wrapper-footer { @include box-shadow(inset 0 1px 2px $shadow-d1); - margin: ($baseline*1.5) 0 0 0; - padding: $baseline; - position: absolute; - bottom: 0; + position: relative; width: 100%; - height: $footer-primary-height; - background: $gray-l3; + padding: $baseline; + background: $gray-l4; footer.primary { @include clearfix(); @@ -55,8 +52,6 @@ .ss-icon { @include transition(top .25s ease-in-out .25s); @include font-size(15); - position: relative; - top: 0; display: inline-block; vertical-align: middle; margin-right: ($baseline/4); @@ -67,133 +62,15 @@ color: $gray-d2; .ss-icon { - top: -($baseline/10); color: $gray-d2; } } - } - } - } - } - // sock - additional help - .sock { - @include clearfix(); - @extend .t-copy-sub2; - max-width: $fg-max-width; - min-width: $fg-min-width; - width: flex-grid(12); - margin: 0 auto $baseline auto; - border-bottom: 1px solid $gray-l2; - padding-bottom: $baseline; - - header { - - .title { - @extend .t-title-3; - } - - .ss-icon { - @extend .t-icon; - @extend .icon-inline; - } - } - - // shared elements - .support, .feedback { - @include box-sizing(border-box); - - .title { - - } - - .copy { - margin: 0 0 $baseline 0; - } - - .list-actions { - @include clearfix(); - - .action-item { - float: left; - margin-right: ($baseline/2); - - &:last-child { - margin-right: 0; - } - - .action { - display: block; - - .ss-icon { - @include font-size(15); - @extend .t-icon; - @extend .icon-inline; - } - - &:hover, &:active { - - .ss-icon { - } - } - } - - .tip { - @extend .sr; + &.is-active { + color: $gray-d2; } } - - .action-primary { - @include gray-button; - @include transition(all .15s); - @include font-size(13); - font-weight: 500; - padding: ($baseline/4) ($baseline/2); - text-align: center; - } } } - - // studio support content - .support { - width: flex-grid(8,12); - float: left; - margin-right: flex-gutter(); - - .action-item { - width: flexgrid(4,8); - } - } - - // studio feedback content - .feedback { - width: flex-grid(4,12); - float: left; - - .action-item { - width: flexgrid(4,4); - } - } - } -} - - -// js-enabled styling -.js .wrapper-footer { - @include transition(height 2s ease-in-out); - height: $footer-primary-height; - overflow: hidden; - - .sock { - display: none; - } -} - - // expanded view -.js.footer-is-expanded .wrapper-footer { - height: ($footer-primary-height*4); - - .sock { - display: block; } } \ No newline at end of file diff --git a/cms/static/sass/elements/_header.scss b/cms/static/sass/elements/_header.scss index e8df37f57f..12c736de7d 100644 --- a/cms/static/sass/elements/_header.scss +++ b/cms/static/sass/elements/_header.scss @@ -2,7 +2,7 @@ // ==================== .wrapper-header { - margin: 0 0 ($baseline*1.5) 0; + margin: 0; padding: $baseline; border-bottom: 1px solid $gray; @include box-shadow(0 1px 5px 0 rgba(0,0,0, 0.1)); diff --git a/cms/static/sass/elements/_sock.scss b/cms/static/sass/elements/_sock.scss new file mode 100644 index 0000000000..27f43935f6 --- /dev/null +++ b/cms/static/sass/elements/_sock.scss @@ -0,0 +1,152 @@ +// studio - elements - support sock +// ==================== + +.wrapper-sock { + @include transition(background 0.25s ease-in-out); + @include clearfix(); + position: relative; + width: 100%; + margin: ($baseline*2.5) 0; + padding: 0 $baseline; + border-top: 1px solid $gray-l4; + + // actions + .list-cta { + position: relative; + top: -($baseline); + margin: 0 auto; + text-align: center; + + .cta-show-sock { + @extend .btn-secondary-gray; + @extend .t-action3; + background: $gray-l5; + padding: ($baseline/2) $baseline; + + .icon { + @include font-size(17); + } + } + } + + // sock - additional help + .sock { + @include clearfix(); + @extend .t-copy-sub2; + display: none; + opacity: 0.0; + pointer-events: none; + max-width: $fg-max-width; + min-width: $fg-min-width; + width: flex-grid(12); + margin: 0 auto; + color: $gray-l3; + + // support body + header { + + .title { + @extend .t-title-3; + margin-bottom: ($baseline/2); + } + + .ss-icon { + @extend .t-icon; + @extend .icon-inline; + } + } + + // shared elements + .support, .feedback { + @include box-sizing(border-box); + + .title { + @extend .t-title-3; + color: $white; + } + + .copy { + margin: 0 0 $baseline 0; + } + + .list-actions { + @include clearfix(); + + .action-item { + float: left; + margin-right: ($baseline/2); + + &:last-child { + margin-right: 0; + } + + .action { + display: block; + + .icon { + @include font-size(18); + } + + &:hover, &:active { + + } + } + + .tip { + @extend .sr; + } + } + + .action-primary { + @extend .btn-primary-blue; + @extend .t-action3; + } + } + } + + // studio support content + .support { + width: flex-grid(8,12); + float: left; + margin-right: flex-gutter(); + + .action-item { + width: flexgrid(4,8); + } + } + + // studio feedback content + .feedback { + width: flex-grid(4,12); + float: left; + + .action-item { + width: flexgrid(4,4); + } + } + } +} + +// case: sock content is shown +.sock-is-shown { + + .wrapper-sock { + @include linear-gradient($gray-d4 0%, $gray-d3 2%, $gray-d3 98%, $gray-d4 100%); + border-bottom: 1px solid $white; + border-top: 1px solid $white; + padding-bottom: ($baseline*2); + padding-top: ($baseline*2); + + .cta-show-sock { + display: none; + opacity: 0.0; + pointer-events: none; + } + + .sock { + display: block; + opacity: 1.0; + pointer-events: auto; + } + } +} \ No newline at end of file diff --git a/cms/static/sass/elements/_typography.scss b/cms/static/sass/elements/_typography.scss index a9b3d362ee..32c4b3928b 100644 --- a/cms/static/sass/elements/_typography.scss +++ b/cms/static/sass/elements/_typography.scss @@ -7,18 +7,16 @@ } .t-title-1 { - @include font-size(32); + @include font-size(36); } .t-title-2 { @include font-size(24); - margin: 0 0 ($baseline/2) 0; font-weight: 600; } .t-title-3 { @include font-size(16); - margin: 0 0 ($baseline/2) 0; font-weight: 600; } @@ -60,18 +58,23 @@ // ==================== // actions/labels -.t-action { +.t-action1 { @include font-size(14); font-weight: 600; } -.t-action-primary { - @include font-size(14); - font-weight: 600; -} - -.t-action-primary-s { +.t-action2 { @include font-size(13); + font-weight: 600; + text-transform: uppercase; +} + +.t-action3 { + @include font-size(13); +} + +.t-action4 { + @include font-size(12); } // ==================== diff --git a/cms/static/sass/views/_index.scss b/cms/static/sass/views/_index.scss index 88beccc82d..7c45530339 100644 --- a/cms/static/sass/views/_index.scss +++ b/cms/static/sass/views/_index.scss @@ -296,8 +296,7 @@ body.index { // call to action content .wrapper-content-cta { position: relative; - padding-bottom: ($footer-primary-height*2); - padding-top: ($baseline*2); + padding: ($baseline*2) 0; background: $white; } diff --git a/cms/templates/base.html b/cms/templates/base.html index e587619bae..46d566116e 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -53,10 +53,17 @@
    <%include file="widgets/header.html" /> +
    - <%block name="content"> + <%block name="content"> + % if user.is_authenticated(): + <%include file="widgets/sock.html" /> + % endif +
    + +
    + <%include file="widgets/footer.html" />
    - <%include file="widgets/footer.html" />
    <%include file="widgets/tender.html" /> diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index b6a3b84272..eea155ce19 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -1,49 +1,5 @@ <%! from django.core.urlresolvers import reverse %> - - @@ -251,7 +279,7 @@

    We're sorry, there was a error with Studio

    -

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

    +

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.

    @@ -263,7 +291,7 @@
    @@ -320,7 +348,7 @@

    Your Studio account has been created, but needs to be activated

    -

    Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.

    +

    Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Curabitur blandit tempus porttitor.

    + -
    +
    @@ -172,40 +185,39 @@ from contentstore import utils
  • -
    - +
    -
    +
    Enter your YouTube video's ID (along with any restriction parameters) -
    +
  • -
    +
    -
    +

    Requirements

    Expectations of the students taking this course -
    +
    1. - Time spent on all course work -
    2. -
    -
    + Time spent on all course work + + + @@ -215,7 +227,7 @@ from contentstore import utils

    Your course's schedule settings determine when students can enroll in and begin a course as well as when the course.

    Additionally, details provided on this page are also used in edX's catalog of courses, which new and returning students use to choose new courses to study.

    -
    +
    % if context_course: @@ -234,4 +246,4 @@ from contentstore import utils
    - \ No newline at end of file + From e3a1279c8f2229bab759367976ffb6cfdf6d8a66 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 8 Apr 2013 14:10:38 -0400 Subject: [PATCH 355/483] studio - corrected a typo and adjusted copy on the help sock --- cms/templates/widgets/sock.html | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cms/templates/widgets/sock.html b/cms/templates/widgets/sock.html index b47b88a0ee..823624c531 100644 --- a/cms/templates/widgets/sock.html +++ b/cms/templates/widgets/sock.html @@ -5,20 +5,20 @@ Looking for Help with Studio? - +

    edX Studio Help

    - +

    Studio Support

    - +
    -

    Need help with Studio? Creating a course is complex, so we're here to help? Take advantage of our documentation, help center, as well as our edX101 introduction course for course authors.

    +

    Need help with Studio? Creating a course is complex, so we're here to help. Take advantage of our documentation, help center, as well as our edX101 introduction course for course authors.

    - +
    - +
    -
    -
    \ No newline at end of file +
    +
    From 069f997595171a94457ada259ff5567617cd9efa Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 8 Apr 2013 14:33:46 -0400 Subject: [PATCH 356/483] import was putting the xml data into definition.data.data rather than definition.data --- .../lib/xmodule/xmodule/modulestore/xml_importer.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 53eaebf850..2047f016ae 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -284,7 +284,7 @@ def import_from_xml(store, data_dir, course_dirs=None, except KeyError: # Ignore any missing keys in _model_data pass - + if 'data' in content: module_data = content['data'] @@ -301,16 +301,17 @@ def import_from_xml(store, data_dir, course_dirs=None, # Note the dropped element closing tag. This causes the LMS to fail when rendering modules - that's # no good, so we have to do this kludge if isinstance(module_data, str) or isinstance(module_data, unicode): # some module 'data' fields are non strings which blows up the link traversal code - lxml_rewrite_links(module_data, lambda link: verify_content_links(module, course_data_path, - static_content_store, link, remap_dict)) + lxml_rewrite_links(module_data, lambda link: + verify_content_links(module, course_data_path, + static_content_store, link, remap_dict)) for key in remap_dict.keys(): module_data = module_data.replace(key, remap_dict[key]) - except Exception, e: + except Exception: logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location)) - store.update_item(module.location, content) + store.update_item(module.location, module_data) if hasattr(module, 'children') and module.children != []: store.update_children(module.location, module.children) From 82882020080c23c82919d804963ac767bb79ba4f Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 8 Apr 2013 14:42:15 -0400 Subject: [PATCH 357/483] Marketing request: Lighthouse [#338, fixed] --- .../images/university/delft/delft-cover.jpg | Bin 197366 -> 231238 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lms/static/images/university/delft/delft-cover.jpg b/lms/static/images/university/delft/delft-cover.jpg index e69c836908809035fb829672efaf530b684cc26a..fe41928b41b44a7c951cf0e1ffc2ed9f7583f3e2 100644 GIT binary patch literal 231238 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 _^(R?!y74jqO^2vYrfvR~9f0D(|pB4gw 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 Oj$k{)I|s!4xA=bz+NW&* literal 197366 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

    tFgg^jU4LW$hrk_*T+gMs@%B+D32N(jHLA?MmP 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 z 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 Date: Mon, 8 Apr 2013 14:48:16 -0400 Subject: [PATCH 358/483] fix line breaks per Cale --- common/lib/xmodule/xmodule/modulestore/xml_importer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 2047f016ae..1a48bc647c 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -301,9 +301,8 @@ def import_from_xml(store, data_dir, course_dirs=None, # Note the dropped element closing tag. This causes the LMS to fail when rendering modules - that's # no good, so we have to do this kludge if isinstance(module_data, str) or isinstance(module_data, unicode): # some module 'data' fields are non strings which blows up the link traversal code - lxml_rewrite_links(module_data, lambda link: - verify_content_links(module, course_data_path, - static_content_store, link, remap_dict)) + lxml_rewrite_links(module_data, + lambda link: verify_content_links(module, course_data_path, static_content_store, link, remap_dict)) for key in remap_dict.keys(): module_data = module_data.replace(key, remap_dict[key]) From c12d265a21ba72d940a57370c5638d62264c6f39 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 8 Apr 2013 15:00:54 -0400 Subject: [PATCH 359/483] calling update_item implicitly already puts the data in definition.data, so we want to make sure we don't end up nesting definition.data.data --- common/lib/xmodule/xmodule/modulestore/xml_importer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 3626bc819d..cd84d2199d 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -327,10 +327,10 @@ def import_module(module, store, course_data_path, static_content_store, allow_n except Exception, e: logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location)) - if allow_not_found: - store.update_item(module.location, content, allow_not_found=allow_not_found) - else: - store.update_item(module.location, content) + if allow_not_found: + store.update_item(module.location, module_data, allow_not_found=allow_not_found) + else: + store.update_item(module.location, module_data) if hasattr(module, 'children') and module.children != []: store.update_children(module.location, module.children) From 4cbb92533f8237018875e1f9e2f0e9486d6d4572 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 8 Apr 2013 15:08:05 -0400 Subject: [PATCH 360/483] fix broken unit test --- .../lib/xmodule/xmodule/modulestore/xml_importer.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index cd84d2199d..a29b56f5b6 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -302,6 +302,7 @@ def import_module(module, store, course_data_path, static_content_store, allow_n # Ignore any missing keys in _model_data pass + module_data = {} if 'data' in content: module_data = content['data'] @@ -324,13 +325,15 @@ def import_module(module, store, course_data_path, static_content_store, allow_n for key in remap_dict.keys(): module_data = module_data.replace(key, remap_dict[key]) - except Exception, e: + except Exception: logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location)) + else: + module_data = content - if allow_not_found: - store.update_item(module.location, module_data, allow_not_found=allow_not_found) - else: - store.update_item(module.location, module_data) + if allow_not_found: + store.update_item(module.location, module_data, allow_not_found=allow_not_found) + else: + store.update_item(module.location, module_data) if hasattr(module, 'children') and module.children != []: store.update_children(module.location, module.children) From 63b5153cde06c36f9b8011f66fe2cf16730198c1 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 8 Apr 2013 15:08:23 -0400 Subject: [PATCH 361/483] Clean up violations from ichuang's PR --- .pylintrc | 8 +++- .../management/commands/dump_grades.py | 11 ++--- lms/djangoapps/instructor/views.py | 42 +++++++++---------- lms/djangoapps/open_ended_grading/views.py | 2 - 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/.pylintrc b/.pylintrc index 2f2be69eb0..49fcf80eb9 100644 --- a/.pylintrc +++ b/.pylintrc @@ -35,6 +35,7 @@ load-plugins= # it should appear only once). disable= # C0301: Line too long +# C0302: Too many lines in module # W0141: Used builtin function 'map' # W0142: Used * or ** magic # R0201: Method could be a function @@ -42,8 +43,11 @@ disable= # R0902: Too many instance attributes # R0903: Too few public methods (1/2) # R0904: Too many public methods +# R0911: Too many return statements +# R0912: Too many branches # R0913: Too many arguments - C0301,W0141,W0142,R0201,R0901,R0902,R0903,R0904,R0913 +# R0914: Too many local variables + C0301,C0302,W0141,W0142,R0201,R0901,R0902,R0903,R0904,R0911,R0912,R0913,R0914 [REPORTS] @@ -92,7 +96,7 @@ zope=no # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. Python regular # expressions are accepted. -generated-members=REQUEST,acl_users,aq_parent,objects,DoesNotExist,can_read,can_write,get_url,size +generated-members=REQUEST,acl_users,aq_parent,objects,DoesNotExist,can_read,can_write,get_url,size,content [BASIC] diff --git a/lms/djangoapps/instructor/management/commands/dump_grades.py b/lms/djangoapps/instructor/management/commands/dump_grades.py index 13f86c0e0f..3707ad33ed 100644 --- a/lms/djangoapps/instructor/management/commands/dump_grades.py +++ b/lms/djangoapps/instructor/management/commands/dump_grades.py @@ -3,17 +3,12 @@ # django management command: dump grades to csv files # for use by batch processes -import os -import sys -import string -import datetime -import json +import csv -from instructor.views import * +from instructor.views import get_student_grade_summary_data from courseware.courses import get_course_by_id from xmodule.modulestore.django import modulestore -from django.conf import settings from django.core.management.base import BaseCommand @@ -45,7 +40,7 @@ class Command(BaseCommand): request = self.DummyRequest() try: course = get_course_by_id(course_id) - except Exception as err: + except Exception: if course_id in modulestore().courses: course = modulestore().courses[course_id] else: diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index 762b993504..a3b4f42bf7 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -11,7 +11,6 @@ import requests from requests.status_codes import codes import urllib from collections import OrderedDict -import json from StringIO import StringIO @@ -21,7 +20,6 @@ from django.http import HttpResponse from django_future.csrf import ensure_csrf_cookie from django.views.decorators.cache import cache_control from mitxmako.shortcuts import render_to_response -import requests from django.core.urlresolvers import reverse from courseware import grades @@ -36,11 +34,7 @@ from django_comment_client.models import (Role, from django_comment_client.utils import has_forum_access from psychometrics import psychoanalyze from student.models import CourseEnrollment, CourseEnrollmentAllowed -from xmodule.course_module import CourseDescriptor -from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem -from xmodule.modulestore.search import path_to_location import xmodule.graders as xmgraders import track.views @@ -48,14 +42,15 @@ from .offline_gradecalc import student_grades, offline_grades_available log = logging.getLogger(__name__) -template_imports = {'urllib': urllib} - # internal commands for managing forum roles: FORUM_ROLE_ADD = 'add' FORUM_ROLE_REMOVE = 'remove' def split_by_comma_and_whitespace(s): + """ + Return string s, split by , or whitespace + """ return re.split(r'[\s,]', s) @@ -141,7 +136,7 @@ def instructor_dashboard(request, course_id): # 'beta', so adding it to get_access_group_name doesn't really make # sense. name = course_beta_test_group_name(course.location) - (group, created) = Group.objects.get_or_create(name=name) + (group, _) = Group.objects.get_or_create(name=name) return group # process actions from form POST @@ -237,13 +232,13 @@ def instructor_dashboard(request, course_id): if '/' not in problem_to_reset: # allow state of modules other than problem to be reset problem_to_reset = "problem/" + problem_to_reset # but problem is the default try: - (org, course_name, run) = course_id.split("/") + (org, course_name, _) = course_id.split("/") module_state_key = "i4x://" + org + "/" + course_name + "/" + problem_to_reset module_to_reset = StudentModule.objects.get(student_id=student_to_reset.id, course_id=course_id, module_state_key=module_state_key) msg += "Found module to reset. " - except Exception as e: + except Exception: msg += "Couldn't find module with that urlname. " if "Delete student state for problem" in action: @@ -352,7 +347,7 @@ def instructor_dashboard(request, course_id): return_csv('', datatable, fp=fp) fp.seek(0) files = {'datafile': fp} - msg2, dataset = _do_remote_gradebook(request.user, course, 'post-grades', files=files) + msg2, _ = _do_remote_gradebook(request.user, course, 'post-grades', files=files) msg += msg2 @@ -423,7 +418,7 @@ def instructor_dashboard(request, course_id): datatable = {'header': ['username', 'email'] + profkeys} def getdat(u): p = u.profile - return [u.username, u.email] + [getattr(p,x,'') for x in profkeys] + return [u.username, u.email] + [getattr(p, x, '') for x in profkeys] datatable['data'] = [getdat(u) for u in enrolled_students] datatable['title'] = 'Student profile data for course %s' % course_id @@ -433,17 +428,17 @@ def instructor_dashboard(request, course_id): elif 'Download CSV of all responses to problem' in action: problem_to_dump = request.POST.get('problem_to_dump','') - if problem_to_dump[-4:]==".xml": - problem_to_dump=problem_to_dump[:-4] + if problem_to_dump[-4:] == ".xml": + problem_to_dump = problem_to_dump[:-4] try: - (org, course_name, run)=course_id.split("/") - module_state_key="i4x://"+org+"/"+course_name+"/problem/"+problem_to_dump + (org, course_name, run) = course_id.split("/") + module_state_key = "i4x://" + org + "/" + course_name + "/problem/" + problem_to_dump smdat = StudentModule.objects.filter(course_id=course_id, module_state_key=module_state_key) smdat = smdat.order_by('student') msg += "Found %d records to dump " % len(smdat) except Exception as err: - msg+="Couldn't find module with that urlname. " + msg += "Couldn't find module with that urlname. " msg += "

    %s
    " % escape(err) smdat = [] @@ -741,7 +736,7 @@ def _list_course_forum_members(course_id, rolename, datatable): # make sure datatable is set up properly for display first, before checking for errors datatable['header'] = ['Username', 'Full name', 'Roles'] datatable['title'] = 'List of Forum {0}s in course {1}'.format(rolename, course_id) - datatable['data'] = []; + datatable['data'] = [] try: role = Role.objects.get(name=rolename, course_id=course_id) except Role.DoesNotExist: @@ -923,7 +918,7 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, datarow = [student.id, student.username, student.profile.name, student.email] try: datarow.append(student.externalauthmap.external_email) - except: # ExternalAuthMap.DoesNotExist + except: # ExternalAuthMap.DoesNotExist datarow.append('') if get_grades: @@ -1040,7 +1035,8 @@ def _do_enroll_students(course, course_id, students, overload=False): datatable['data'] = [[x, status[x]] for x in status] datatable['title'] = 'Enrollment of students' - def sf(stat): return [x for x in status if status[x] == stat] + def sf(stat): + return [x for x in status if status[x] == stat] data = dict(added=sf('added'), rejected=sf('rejected') + sf('exists'), deleted=sf('deleted'), datatable=datatable) @@ -1136,7 +1132,7 @@ def dump_grading_context(course): ''' msg = "-----------------------------------------------------------------------------\n" msg += "Course grader:\n" - + msg += '%s\n' % course.grader.__class__ graders = {} if isinstance(course.grader, xmgraders.WeightedSubsectionsGrader): @@ -1151,7 +1147,7 @@ def dump_grading_context(course): gc = course.grading_context msg += "graded sections:\n" - + msg += '%s\n' % gc['graded_sections'].keys() for (gs, gsvals) in gc['graded_sections'].items(): msg += "--> Section %s:\n" % (gs) diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 78da00bf2b..cb617d609d 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -27,8 +27,6 @@ from mitxmako.shortcuts import render_to_string log = logging.getLogger(__name__) -template_imports = {'urllib': urllib} - system = ModuleSystem( ajax_url=None, track_function=None, From a0f5a51135274241f2f469078b8f75222f973249 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 8 Apr 2013 15:34:12 -0400 Subject: [PATCH 362/483] wip to fix broken test --- common/lib/xmodule/xmodule/modulestore/xml_importer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 1a48bc647c..97b3396baa 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -309,6 +309,8 @@ def import_from_xml(store, data_dir, course_dirs=None, except Exception: logging.exception("failed to rewrite links on {0}. Continuing...".format(module.location)) + else: + module_data = content store.update_item(module.location, module_data) From 4e331a2c6a2999ad7847212a00135e5810121f45 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 8 Apr 2013 15:39:01 -0400 Subject: [PATCH 363/483] studio - restyling course basic details and adding in a course's URL more prominently WIP --- cms/static/sass/elements/_controls.scss | 2 +- cms/static/sass/elements/_forms.scss | 39 +++++++++++--- cms/static/sass/views/_settings.scss | 67 ++++++++++++++++++++++++- cms/templates/settings.html | 27 +++++----- 4 files changed, 111 insertions(+), 24 deletions(-) diff --git a/cms/static/sass/elements/_controls.scss b/cms/static/sass/elements/_controls.scss index c4e96616a8..953b2d15e5 100644 --- a/cms/static/sass/elements/_controls.scss +++ b/cms/static/sass/elements/_controls.scss @@ -97,7 +97,7 @@ color: $blue; &:hover, &:active { - background: $blue-l3; + background: $blue-l4; color: $blue-s2; } diff --git a/cms/static/sass/elements/_forms.scss b/cms/static/sass/elements/_forms.scss index 3bda079473..1faf4a883e 100644 --- a/cms/static/sass/elements/_forms.scss +++ b/cms/static/sass/elements/_forms.scss @@ -8,11 +8,11 @@ input[type="password"], textarea.text { padding: 6px 8px 8px; @include box-sizing(border-box); - border: 1px solid $mediumGrey; + border: 1px solid $gray-l2; border-radius: 2px; - @include linear-gradient($lightGrey, tint($lightGrey, 90%)); - background-color: $lightGrey; - @include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset); + @include linear-gradient($gray-l5, $white); + background-color: $gray-l5; + @include box-shadow(inset 0 1px 2px $shadow-l1); font-family: 'Open Sans', sans-serif; font-size: 11px; color: $baseFontColor; @@ -21,7 +21,7 @@ textarea.text { &::-webkit-input-placeholder, &:-moz-placeholder, &:-ms-input-placeholder { - color: #979faf; + color: $gray-l2; } &:focus { @@ -39,7 +39,7 @@ textarea.text { color: $gray-d2; } - input, textarea { + label, input, textarea { pointer-events: none; } } @@ -61,8 +61,31 @@ form { .note { @include box-sizing(border-box); - padding: $baseline; - background: $gray-l4; + + .title { + + } + + .copy { + + } + + // note with actions + &.has-actions { + @include clearfix(); + + .title { + + } + + .copy { + + } + + .list-actions { + + } + } } .note-promotion { diff --git a/cms/static/sass/views/_settings.scss b/cms/static/sass/views/_settings.scss index 6b8bec7912..87d076ab72 100644 --- a/cms/static/sass/views/_settings.scss +++ b/cms/static/sass/views/_settings.scss @@ -147,7 +147,7 @@ body.course.settings { } label { - @include font-size(14); + @extend .t-copy-sub1; @include transition(color, 0.15s, ease-in-out); margin: 0 0 ($baseline/4) 0; font-weight: 400; @@ -243,12 +243,31 @@ body.course.settings { .list-input { @include clearfix(); + padding: 0 ($baseline/2); .field { margin-bottom: 0; } } + // course details that should appear more like content than elements to change + .field.is-not-editable { + + label { + + } + + input, textarea { + @extend .t-copy-lead1; + @include box-shadow(none); + border: none; + background: none; + padding: 0; + margin: 0; + font-weight: 600; + } + } + #field-course-organization { float: left; width: flex-grid(2, 9); @@ -265,6 +284,52 @@ body.course.settings { float: left; width: flex-grid(5, 9); } + + // course link note + .note-promotion-courseURL { + @include box-shadow(0 1px 1px $shadow-l1); + @include border-radius(($baseline/5)); + margin-top: ($baseline*1.5); + border: 1px solid $gray-l3; + padding: ($baseline/2) 0 0 0; + + .title { + @extend .t-copy-sub1; + margin: 0 0 ($baseline/10) 0; + padding: 0 ($baseline/2); + } + + .copy { + padding: 0 ($baseline/2) ($baseline/2) ($baseline/2); + + .link-courseURL { + @extend .t-copy-lead1; + + &:hover { + + } + } + } + + .list-actions { + @include box-shadow(inset 0 1px 1px $shadow-l1); + border-top: 1px solid $gray-l4; + padding: ($baseline/2); + background: $gray-l5; + + .action-primary { + @include blue-button(); + @include font-size(13); + + .icon { + @extend .t-icon; + @include font-size(16); + display: inline-block; + vertical-align: middle; + } + } + } + } } // specific fields - schedule diff --git a/cms/templates/settings.html b/cms/templates/settings.html index afc958c608..9e16c15c0a 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -67,20 +67,6 @@ from contentstore import utils The nuts and bolts of your course - -
    1. @@ -97,6 +83,19 @@ from contentstore import utils
    + +
    +

    Course URL (for student enrollment and access)

    + + + +

    From 54afc5eb6331c700936a7cca6e9b15e60abbf4ca Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 8 Apr 2013 16:09:41 -0400 Subject: [PATCH 364/483] seems like we need to purge 'filename' from both _model_data as well as xml_attributes --- common/lib/xmodule/xmodule/tests/test_export.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py index 443014f9ef..170a89d783 100644 --- a/common/lib/xmodule/xmodule/tests/test_export.py +++ b/common/lib/xmodule/xmodule/tests/test_export.py @@ -24,6 +24,11 @@ def strip_filenames(descriptor): """ print "strip filename from {desc}".format(desc=descriptor.location.url()) descriptor._model_data.pop('filename', None) + + if hasattr(descriptor, 'xml_attributes'): + if 'filename' in descriptor.xml_attributes: + del descriptor.xml_attributes['filename'] + for d in descriptor.get_children(): strip_filenames(d) From 3f22826e26c85033daaebc3914a0946256d21605 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Mon, 8 Apr 2013 16:28:31 -0400 Subject: [PATCH 365/483] studio - alerts: adding some ARIA-centric accessibility information to example prompts/notifications and advanced settings save notification --- cms/static/js/base.js | 2 +- cms/static/js/views/settings/advanced_view.js | 8 +-- cms/templates/settings_advanced.html | 10 +-- cms/templates/ux-alerts.html | 62 ++++++++----------- 4 files changed, 36 insertions(+), 46 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index bb3fe88510..6d047050be 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -545,7 +545,7 @@ function removeDateSetter(e) { function hideNotification(e) { (e).preventDefault(); - $(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding'); + $(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden','true'); } function hideAlert(e) { diff --git a/cms/static/js/views/settings/advanced_view.js b/cms/static/js/views/settings/advanced_view.js index 536f23004e..9c62499773 100644 --- a/cms/static/js/views/settings/advanced_view.js +++ b/cms/static/js/views/settings/advanced_view.js @@ -104,10 +104,10 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ $(".wrapper-alert").removeClass("is-shown"); if (type) { if (type === this.error_saving) { - $(".wrapper-alert-error").addClass("is-shown"); + $(".wrapper-alert-error").addClass("is-shown").attr('aria-hidden','false'); } else if (type === this.successful_changes) { - $(".wrapper-alert-confirmation").addClass("is-shown"); + $(".wrapper-alert-confirmation").addClass("is-shown").attr('aria-hidden','false'); this.hideSaveCancelButtons(); } } @@ -119,13 +119,13 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ showSaveCancelButtons: function(event) { if (!this.notificationBarShowing) { this.$el.find(".message-status").removeClass("is-shown"); - $('.wrapper-notification').removeClass('is-hiding').addClass('is-shown'); + $('.wrapper-notification').removeClass('is-hiding').addClass('is-shown').attr('aria-hidden','false'); this.notificationBarShowing = true; } }, hideSaveCancelButtons: function() { if (this.notificationBarShowing) { - $('.wrapper-notification').removeClass('is-shown').addClass('is-hiding'); + $('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden','true'); this.notificationBarShowing = false; } }, diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html index 4cd1a4acd9..034f1a32e2 100644 --- a/cms/templates/settings_advanced.html +++ b/cms/templates/settings_advanced.html @@ -108,13 +108,13 @@ editor.render(); <%block name="view_notifications"> -
    +