From 57eca325d2f170a3eb1f07aaa9f6cc4ef7693afb Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 15:59:35 -0400 Subject: [PATCH 01/57] move some files from lms/djangoapps/django_comment_client to common/djangoapps/django_comment_common --- .../django_comment_common}/__init__.py | 0 .../migrations/0001_initial.py | 0 .../migrations/__init__.py | 0 .../django_comment_common}/models.py | 0 .../djangoapps/django_comment_common/utils.py | 27 +++++++++++++++++++ 5 files changed, 27 insertions(+) rename {lms/djangoapps/django_comment_client/migrations => common/djangoapps/django_comment_common}/__init__.py (100%) rename {lms/djangoapps/django_comment_client => common/djangoapps/django_comment_common}/migrations/0001_initial.py (100%) create mode 100644 common/djangoapps/django_comment_common/migrations/__init__.py rename {lms/djangoapps/django_comment_client => common/djangoapps/django_comment_common}/models.py (100%) create mode 100644 common/djangoapps/django_comment_common/utils.py diff --git a/lms/djangoapps/django_comment_client/migrations/__init__.py b/common/djangoapps/django_comment_common/__init__.py similarity index 100% rename from lms/djangoapps/django_comment_client/migrations/__init__.py rename to common/djangoapps/django_comment_common/__init__.py diff --git a/lms/djangoapps/django_comment_client/migrations/0001_initial.py b/common/djangoapps/django_comment_common/migrations/0001_initial.py similarity index 100% rename from lms/djangoapps/django_comment_client/migrations/0001_initial.py rename to common/djangoapps/django_comment_common/migrations/0001_initial.py diff --git a/common/djangoapps/django_comment_common/migrations/__init__.py b/common/djangoapps/django_comment_common/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/django_comment_client/models.py b/common/djangoapps/django_comment_common/models.py similarity index 100% rename from lms/djangoapps/django_comment_client/models.py rename to common/djangoapps/django_comment_common/models.py diff --git a/common/djangoapps/django_comment_common/utils.py b/common/djangoapps/django_comment_common/utils.py new file mode 100644 index 0000000000..9753591c5c --- /dev/null +++ b/common/djangoapps/django_comment_common/utils.py @@ -0,0 +1,27 @@ +from django_comment_common.models import Role + + +def seed_permissions_roles(course_id): + administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0] + moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0] + community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0] + student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0] + + for per in ["vote", "update_thread", "follow_thread", "unfollow_thread", + "update_comment", "create_sub_comment", "unvote", "create_thread", + "follow_commentable", "unfollow_commentable", "create_comment", ]: + student_role.add_permission(per) + + for per in ["edit_content", "delete_thread", "openclose_thread", + "endorse_comment", "delete_comment", "see_all_cohorts"]: + moderator_role.add_permission(per) + + for per in ["manage_moderator"]: + administrator_role.add_permission(per) + + moderator_role.inherit_permissions(student_role) + + # For now, Community TA == Moderator, except for the styling. + community_ta_role.inherit_permissions(moderator_role) + + administrator_role.inherit_permissions(moderator_role) \ No newline at end of file From 30d266b43088d65594339db5d7a89aa0f009503c Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 16:01:14 -0400 Subject: [PATCH 02/57] seed forum permissions when creating new courses --- cms/djangoapps/contentstore/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index caf3901e03..af31fb841b 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -64,6 +64,8 @@ from contentstore.utils import get_modulestore from django.shortcuts import redirect from models.settings.course_metadata import CourseMetadata +from django_comment_common.utils import seed_permissions_roles + # to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' log = logging.getLogger(__name__) @@ -1503,6 +1505,9 @@ def create_new_course(request): create_all_course_groups(request.user, new_course.location) + # seed the forums + seed_permissions_roles(new_course.location.course_id) + return HttpResponse(json.dumps({'id': new_course.location.url()})) From 3858f1b7063bda75ec6a1f44800afd291ad3fd4d Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 16:02:13 -0400 Subject: [PATCH 03/57] update cms envs --- cms/envs/common.py | 3 +++ cms/envs/dev.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 8effc773e0..3d4f2d2e9d 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -263,4 +263,7 @@ INSTALLED_APPS = ( 'pipeline', 'staticfiles', 'static_replace', + + # comment common + 'django_comment_common', ) diff --git a/cms/envs/dev.py b/cms/envs/dev.py index dbf9c5574c..f3f972be42 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -118,8 +118,7 @@ PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon ################################ DEBUG TOOLBAR ################################# INSTALLED_APPS += ('debug_toolbar', 'debug_toolbar_mongo') -MIDDLEWARE_CLASSES += ('django_comment_client.utils.QueryCountDebugMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware',) +MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) INTERNAL_IPS = ('127.0.0.1',) DEBUG_TOOLBAR_PANELS = ( From ae9d13b53aa01b918100a5064330ee8b1a8b1fdc Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 16:04:25 -0400 Subject: [PATCH 04/57] update comment models.py to not have the dependency on get_course_by_id (which is in LMS). Also explicitly set the db_table name because django auto generates table prefixes according to the djangoapp name --- common/djangoapps/django_comment_common/models.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/django_comment_common/models.py b/common/djangoapps/django_comment_common/models.py index e06aed1281..6688470753 100644 --- a/common/djangoapps/django_comment_common/models.py +++ b/common/djangoapps/django_comment_common/models.py @@ -8,7 +8,8 @@ from django.db.models.signals import post_save from student.models import CourseEnrollment -from courseware.courses import get_course_by_id +from xmodule.modulestore.django import modulestore +from xmodule.course_module import CourseDescriptor FORUM_ROLE_ADMINISTRATOR = 'Administrator' FORUM_ROLE_MODERATOR = 'Moderator' @@ -32,6 +33,9 @@ class Role(models.Model): users = models.ManyToManyField(User, related_name="roles") course_id = models.CharField(max_length=255, blank=True, db_index=True) + class Meta: + db_table = 'django_comment_client_role' + def __unicode__(self): return self.name + " for " + (self.course_id if self.course_id else "all courses") @@ -47,7 +51,8 @@ class Role(models.Model): self.permissions.add(Permission.objects.get_or_create(name=permission)[0]) def has_permission(self, permission): - course = get_course_by_id(self.course_id) + course_loc = CourseDescriptor.id_to_location(self.course_id) + course = modulestore().get_instance(self.course_id, course_loc) if self.name == FORUM_ROLE_STUDENT and \ (permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \ (not course.forum_posts_allowed): @@ -60,5 +65,8 @@ class Permission(models.Model): name = models.CharField(max_length=30, null=False, blank=False, primary_key=True) roles = models.ManyToManyField(Role, related_name="permissions") + class Meta: + db_table = 'django_comment_client_permission' + def __unicode__(self): return self.name From a92910db209a46848a5626a56d72994b9d73a651 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 16:23:32 -0400 Subject: [PATCH 05/57] update lms to reflect where comment models.py is now in common --- .../django_comment_client/base/views.py | 2 +- .../management/commands/assign_role.py | 2 +- .../commands/assign_roles_for_course.py | 2 +- .../commands/create_roles_for_existing.py | 2 +- .../commands/seed_permissions_roles.py | 21 ++----------------- .../management/commands/show_permissions.py | 2 +- .../django_comment_client/permissions.py | 2 +- lms/djangoapps/django_comment_client/tests.py | 2 +- .../tests/test_models.py | 2 +- .../django_comment_client/tests/test_utils.py | 2 +- lms/djangoapps/django_comment_client/utils.py | 2 +- lms/djangoapps/instructor/views.py | 2 +- lms/envs/common.py | 1 + 13 files changed, 14 insertions(+), 30 deletions(-) diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 69609dcf01..39c38e70f3 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -26,7 +26,7 @@ from course_groups.cohorts import get_cohort_id, is_commentable_cohorted from django_comment_client.utils import JsonResponse, JsonError, extract, get_courseware_context from django_comment_client.permissions import check_permissions_by_view, cached_has_permission -from django_comment_client.models import Role +from django_comment_common.models import Role log = logging.getLogger(__name__) diff --git a/lms/djangoapps/django_comment_client/management/commands/assign_role.py b/lms/djangoapps/django_comment_client/management/commands/assign_role.py index 655631008f..65da1ea5b4 100644 --- a/lms/djangoapps/django_comment_client/management/commands/assign_role.py +++ b/lms/djangoapps/django_comment_client/management/commands/assign_role.py @@ -1,7 +1,7 @@ from optparse import make_option from django.core.management.base import BaseCommand, CommandError -from django_comment_client.models import Role +from django_comment_common.models import Role from django.contrib.auth.models import User 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 72100738d9..9ef4f3d0b1 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 @@ -7,7 +7,7 @@ Enrollments. from django.core.management.base import BaseCommand, CommandError from student.models import CourseEnrollment -from django_comment_client.models import assign_default_role +from django_comment_common.models import assign_default_role class Command(BaseCommand): 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 d5ba0042fc..037bb292ec 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 @@ -7,7 +7,7 @@ Enrollments. from django.core.management.base import BaseCommand, CommandError from student.models import CourseEnrollment -from django_comment_client.models import assign_default_role +from django_comment_common.models import assign_default_role class Command(BaseCommand): diff --git a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py index 9d6eefd11d..f944146906 100644 --- a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py +++ b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py @@ -1,5 +1,5 @@ from django.core.management.base import BaseCommand, CommandError -from django_comment_client.models import Role +from django_comment_common.utils import seed_permissions_roles class Command(BaseCommand): @@ -18,21 +18,4 @@ class Command(BaseCommand): community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0] student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0] - for per in ["vote", "update_thread", "follow_thread", "unfollow_thread", - "update_comment", "create_sub_comment", "unvote", "create_thread", - "follow_commentable", "unfollow_commentable", "create_comment", ]: - student_role.add_permission(per) - - for per in ["edit_content", "delete_thread", "openclose_thread", - "endorse_comment", "delete_comment", "see_all_cohorts"]: - moderator_role.add_permission(per) - - for per in ["manage_moderator"]: - administrator_role.add_permission(per) - - moderator_role.inherit_permissions(student_role) - - # For now, Community TA == Moderator, except for the styling. - community_ta_role.inherit_permissions(moderator_role) - - administrator_role.inherit_permissions(moderator_role) + seed_permissions_roles(course_id) diff --git a/lms/djangoapps/django_comment_client/management/commands/show_permissions.py b/lms/djangoapps/django_comment_client/management/commands/show_permissions.py index ec3167aa0c..67fc29ea97 100644 --- a/lms/djangoapps/django_comment_client/management/commands/show_permissions.py +++ b/lms/djangoapps/django_comment_client/management/commands/show_permissions.py @@ -1,5 +1,5 @@ from django.core.management.base import BaseCommand, CommandError -from django_comment_client.models import Permission, Role +from django_comment_common.models import Permission, Role from django.contrib.auth.models import User diff --git a/lms/djangoapps/django_comment_client/permissions.py b/lms/djangoapps/django_comment_client/permissions.py index 7d21cc9783..456d5f01d2 100644 --- a/lms/djangoapps/django_comment_client/permissions.py +++ b/lms/djangoapps/django_comment_client/permissions.py @@ -1,4 +1,4 @@ -from .models import Role, Permission +from django_comment_common.models import Role, Permission from django.db.models.signals import post_save from django.dispatch import receiver from student.models import CourseEnrollment diff --git a/lms/djangoapps/django_comment_client/tests.py b/lms/djangoapps/django_comment_client/tests.py index a35df54cd9..dcfd29f3cd 100644 --- a/lms/djangoapps/django_comment_client/tests.py +++ b/lms/djangoapps/django_comment_client/tests.py @@ -6,7 +6,7 @@ from django.test import TestCase from student.models import CourseEnrollment from django_comment_client.permissions import has_permission -from django_comment_client.models import Role +from django_comment_common.models import Role class PermissionsTestCase(TestCase): diff --git a/lms/djangoapps/django_comment_client/tests/test_models.py b/lms/djangoapps/django_comment_client/tests/test_models.py index 6f90b3c4b8..e8083fe954 100644 --- a/lms/djangoapps/django_comment_client/tests/test_models.py +++ b/lms/djangoapps/django_comment_client/tests/test_models.py @@ -1,5 +1,5 @@ import django_comment_client.models as models -import django_comment_client.permissions as permissions +import django_comment_common.permissions as permissions from django.test import TestCase diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index 80b8419d5a..3687e169e7 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -2,7 +2,7 @@ from django.test import TestCase from factory import DjangoModelFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory -from django_comment_client.models import Role, Permission +from django_comment_common.models import Role, Permission import django_comment_client.utils as utils diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 9bfb9a9d0d..6e5c6794c9 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse from django.db import connection from django.http import HttpResponse from django.utils import simplejson -from django_comment_client.models import Role +from django_comment_common.models import Role from django_comment_client.permissions import check_permissions_by_view from xmodule.modulestore.exceptions import NoPathToItem diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index dd6748e691..00b1b918b3 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -27,7 +27,7 @@ from courseware.access import (has_access, get_access_group_name, course_beta_test_group_name) from courseware.courses import get_course_with_access from courseware.models import StudentModule -from django_comment_client.models import (Role, +from django_comment_common.models import (Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA) diff --git a/lms/envs/common.py b/lms/envs/common.py index b804ae2a7a..ba4ba75bfd 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -592,5 +592,6 @@ INSTALLED_APPS = ( # Discussion forums 'django_comment_client', + 'django_comment_common', ) From c20dd7d90373227402f8b75356455c7d693ffa47 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 16:40:25 -0400 Subject: [PATCH 06/57] forgot to remove enough stuff from the django admin path --- .../management/commands/seed_permissions_roles.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py index f944146906..1073d7dbcf 100644 --- a/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py +++ b/lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py @@ -13,9 +13,4 @@ class Command(BaseCommand): raise CommandError("Too many arguments") course_id = args[0] - administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0] - moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0] - community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0] - student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0] - seed_permissions_roles(course_id) From 40e3410121bc3e8548d9ed9d94b1a41b7aaa96b8 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 16:51:36 -0400 Subject: [PATCH 07/57] fix broken unit tests --- lms/djangoapps/django_comment_client/tests/test_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/django_comment_client/tests/test_models.py b/lms/djangoapps/django_comment_client/tests/test_models.py index e8083fe954..1f7e52b0a0 100644 --- a/lms/djangoapps/django_comment_client/tests/test_models.py +++ b/lms/djangoapps/django_comment_client/tests/test_models.py @@ -1,5 +1,5 @@ -import django_comment_client.models as models -import django_comment_common.permissions as permissions +import django_comment_common.models as models +import django_comment_client.permissions as permissions from django.test import TestCase From 5ba22ec89b22fd254584372aa4529095199424e2 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 16:56:37 -0400 Subject: [PATCH 08/57] fix another broken test --- lms/djangoapps/instructor/tests/test_forum_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/instructor/tests/test_forum_admin.py b/lms/djangoapps/instructor/tests/test_forum_admin.py index d2d58fb61c..7b4e729867 100644 --- a/lms/djangoapps/instructor/tests/test_forum_admin.py +++ b/lms/djangoapps/instructor/tests/test_forum_admin.py @@ -9,7 +9,7 @@ from django.test.utils import override_settings from django.contrib.auth.models import Group from django.core.urlresolvers import reverse -from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \ +from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, \ FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT from django_comment_client.utils import has_forum_access From 81ecf0c5df4af24b7db0985a269d2a70b30e49e7 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Tue, 7 May 2013 11:18:15 -0400 Subject: [PATCH 09/57] add unit test to verify forum seeding occurs when creating new courses --- .../contentstore/tests/test_contentstore.py | 10 +++++++ .../djangoapps/django_comment_common/utils.py | 29 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 07b7032e60..b4a9210a26 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -34,6 +34,8 @@ from xmodule.course_module import CourseDescriptor from xmodule.seq_module import SequenceDescriptor from xmodule.modulestore.exceptions import ItemNotFoundError +from django_comment_common.utils import are_permissions_roles_seeded + TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') @@ -598,6 +600,14 @@ class ContentStoreTest(ModuleStoreTestCase): data = parse_json(resp) self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') + def test_create_course_check_forum_seeding(self): + """Test new course creation and verify forum seeding """ + resp = self.client.post(reverse('create_new_course'), self.course_data) + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') + self.assertTrue(are_permissions_roles_seeded('MITx/999/Robot_Super_Course')) + def test_create_course_duplicate_course(self): """Test new course creation - error path""" resp = self.client.post(reverse('create_new_course'), self.course_data) diff --git a/common/djangoapps/django_comment_common/utils.py b/common/djangoapps/django_comment_common/utils.py index 9753591c5c..747e7e2c46 100644 --- a/common/djangoapps/django_comment_common/utils.py +++ b/common/djangoapps/django_comment_common/utils.py @@ -24,4 +24,31 @@ def seed_permissions_roles(course_id): # For now, Community TA == Moderator, except for the styling. community_ta_role.inherit_permissions(moderator_role) - administrator_role.inherit_permissions(moderator_role) \ No newline at end of file + administrator_role.inherit_permissions(moderator_role) + + +def are_permissions_roles_seeded(course_id): + + try: + administrator_role = Role.objects.get(name="Administrator", course_id=course_id) + moderator_role = Role.objects.get(name="Moderator", course_id=course_id) + student_role = Role.objects.get(name="Student", course_id=course_id) + except: + return False + + for per in ["vote", "update_thread", "follow_thread", "unfollow_thread", + "update_comment", "create_sub_comment", "unvote", "create_thread", + "follow_commentable", "unfollow_commentable", "create_comment", ]: + if not student_role.has_permission(per): + return False + + for per in ["edit_content", "delete_thread", "openclose_thread", + "endorse_comment", "delete_comment", "see_all_cohorts"]: + if not moderator_role.has_permission(per): + return False + + for per in ["manage_moderator"]: + if not administrator_role.has_permission(per): + return False + + return True \ No newline at end of file From 3c747f0e6e3bccaeab27169b9950dae30fdc166e Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Tue, 7 May 2013 11:53:02 -0400 Subject: [PATCH 10/57] consolidate the list of permissions --- .../djangoapps/django_comment_common/utils.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/common/djangoapps/django_comment_common/utils.py b/common/djangoapps/django_comment_common/utils.py index 747e7e2c46..f74116d59f 100644 --- a/common/djangoapps/django_comment_common/utils.py +++ b/common/djangoapps/django_comment_common/utils.py @@ -1,5 +1,13 @@ from django_comment_common.models import Role +_STUDENT_ROLE_PERMISSIONS = ["vote", "update_thread", "follow_thread", "unfollow_thread", + "update_comment", "create_sub_comment", "unvote", "create_thread", + "follow_commentable", "unfollow_commentable", "create_comment", ] + +_MODERATOR_ROLE_PERMISSIONS = ["edit_content", "delete_thread", "openclose_thread", + "endorse_comment", "delete_comment", "see_all_cohorts"] + +_ADMINISTRATOR_ROLE_PERMISSIONS = ["manage_moderator"] def seed_permissions_roles(course_id): administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0] @@ -7,16 +15,13 @@ def seed_permissions_roles(course_id): community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0] student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0] - for per in ["vote", "update_thread", "follow_thread", "unfollow_thread", - "update_comment", "create_sub_comment", "unvote", "create_thread", - "follow_commentable", "unfollow_commentable", "create_comment", ]: + for per in _STUDENT_ROLE_PERMISSIONS: student_role.add_permission(per) - for per in ["edit_content", "delete_thread", "openclose_thread", - "endorse_comment", "delete_comment", "see_all_cohorts"]: + for per in _MODERATOR_ROLE_PERMISSIONS: moderator_role.add_permission(per) - for per in ["manage_moderator"]: + for per in _ADMINISTRATOR_ROLE_PERMISSIONS: administrator_role.add_permission(per) moderator_role.inherit_permissions(student_role) @@ -36,19 +41,16 @@ def are_permissions_roles_seeded(course_id): except: return False - for per in ["vote", "update_thread", "follow_thread", "unfollow_thread", - "update_comment", "create_sub_comment", "unvote", "create_thread", - "follow_commentable", "unfollow_commentable", "create_comment", ]: + for per in _STUDENT_ROLE_PERMISSIONS: if not student_role.has_permission(per): return False - for per in ["edit_content", "delete_thread", "openclose_thread", - "endorse_comment", "delete_comment", "see_all_cohorts"]: + for per in _MODERATOR_ROLE_PERMISSIONS + _STUDENT_ROLE_PERMISSIONS: if not moderator_role.has_permission(per): return False - for per in ["manage_moderator"]: + for per in _ADMINISTRATOR_ROLE_PERMISSIONS + _MODERATOR_ROLE_PERMISSIONS + _STUDENT_ROLE_PERMISSIONS: if not administrator_role.has_permission(per): return False - return True \ No newline at end of file + return True From 11244107fd19b0894e056998549307f28c15cf77 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Thu, 9 May 2013 16:16:11 -0400 Subject: [PATCH 11/57] add an empty models.py file to django_comment_client --- lms/djangoapps/django_comment_client/models.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lms/djangoapps/django_comment_client/models.py diff --git a/lms/djangoapps/django_comment_client/models.py b/lms/djangoapps/django_comment_client/models.py new file mode 100644 index 0000000000..e69de29bb2 From d39d44ba492cdcb645d8a8a8e49512bef159dd74 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 10 May 2013 12:58:00 -0400 Subject: [PATCH 12/57] keep migrations in djangoapp_comment_client --- .../djangoapps/django_comment_client}/migrations/0001_initial.py | 0 .../djangoapps/django_comment_client}/migrations/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {common/djangoapps/django_comment_common => lms/djangoapps/django_comment_client}/migrations/0001_initial.py (100%) rename {common/djangoapps/django_comment_common => lms/djangoapps/django_comment_client}/migrations/__init__.py (100%) diff --git a/common/djangoapps/django_comment_common/migrations/0001_initial.py b/lms/djangoapps/django_comment_client/migrations/0001_initial.py similarity index 100% rename from common/djangoapps/django_comment_common/migrations/0001_initial.py rename to lms/djangoapps/django_comment_client/migrations/0001_initial.py diff --git a/common/djangoapps/django_comment_common/migrations/__init__.py b/lms/djangoapps/django_comment_client/migrations/__init__.py similarity index 100% rename from common/djangoapps/django_comment_common/migrations/__init__.py rename to lms/djangoapps/django_comment_client/migrations/__init__.py From 5619306c6b96cf46dd03529dc25d5f090346abd3 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 10 May 2013 13:43:15 -0400 Subject: [PATCH 13/57] add comment that the models.py file has been moved --- lms/djangoapps/django_comment_client/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/djangoapps/django_comment_client/models.py b/lms/djangoapps/django_comment_client/models.py index e69de29bb2..76d27be3bf 100644 --- a/lms/djangoapps/django_comment_client/models.py +++ b/lms/djangoapps/django_comment_client/models.py @@ -0,0 +1 @@ +# This file is intentionally blank. It has been moved to common/djangoapps/django_comment_common From 155ad08b9eb316906d6cbd30f42e34857feb17e2 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 13 May 2013 16:04:20 -0400 Subject: [PATCH 14/57] create forums permissions on course create --- cms/djangoapps/contentstore/views/course.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index ecc271926a..cf642e8256 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -10,16 +10,12 @@ from django.core.urlresolvers import reverse from mitxmako.shortcuts import render_to_response from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import ItemNotFoundError, \ - InvalidLocationError +from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError from xmodule.modulestore import Location -from contentstore.course_info_model import get_course_updates, \ - update_course_updates, delete_course_update -from contentstore.utils import get_lms_link_for_item, \ - add_open_ended_panel_tab, remove_open_ended_panel_tab -from models.settings.course_details import CourseDetails, \ - CourseSettingsEncoder +from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update +from contentstore.utils import get_lms_link_for_item, add_open_ended_panel_tab, remove_open_ended_panel_tab +from models.settings.course_details import CourseDetails, CourseSettingsEncoder from models.settings.course_grading import CourseGradingModel from models.settings.course_metadata import CourseMetadata from auth.authz import create_all_course_groups @@ -30,6 +26,8 @@ from .requests import get_request_method from .tabs import initialize_course_tabs from .component import OPEN_ENDED_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY +from django_comment_common.utils import seed_permissions_roles + # TODO: should explicitly enumerate exports with __all__ @@ -124,6 +122,9 @@ def create_new_course(request): create_all_course_groups(request.user, new_course.location) + # seed the forums + seed_permissions_roles(new_course.location.course_id) + return HttpResponse(json.dumps({'id': new_course.location.url()})) From ef78c99f70fdc25b66d77f518e7f6acefad3a2c9 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Tue, 14 May 2013 10:53:10 -0400 Subject: [PATCH 15/57] pep8/pylint violations fixes --- .../contentstore/tests/test_contentstore.py | 36 +++++++++---------- .../xmodule/xmodule/modulestore/__init__.py | 28 ++++++--------- .../lib/xmodule/xmodule/modulestore/draft.py | 3 +- .../xmodule/modulestore/tests/factories.py | 1 - 4 files changed, 31 insertions(+), 37 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index b89de3b37b..848015a2e7 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -47,7 +47,7 @@ class MongoCollectionFindWrapper(object): self.counter = 0 def find(self, query, *args, **kwargs): - self.counter = self.counter+1 + self.counter = self.counter + 1 return self.original(query, *args, **kwargs) @@ -357,7 +357,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'vertical', None])) self.assertGreater(len(clone_items), 0) for descriptor in items: - new_loc = descriptor.location._replace(org='MITx', course='999') + new_loc = descriptor.location.replace(org='MITx', course='999') print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()})) self.assertEqual(resp.status_code, 200) @@ -380,15 +380,15 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.assertEqual(len(items), 0) def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''): - fs = OSFS(root_dir / 'test_export') - self.assertTrue(fs.exists(dirname)) + filesystem = OSFS(root_dir / 'test_export') + self.assertTrue(filesystem.exists(dirname)) query_loc = Location('i4x', location.org, location.course, category_name, None) items = modulestore.get_items(query_loc) for item in items: - fs = OSFS(root_dir / ('test_export/' + dirname)) - self.assertTrue(fs.exists(item.location.name + filename_suffix)) + filesystem = OSFS(root_dir / ('test_export/' + dirname)) + self.assertTrue(filesystem.exists(item.location.name + filename_suffix)) def test_export_course(self): module_store = modulestore('direct') @@ -420,7 +420,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): # add private to list of children sequential = module_store.get_item(Location(['i4x', 'edX', 'full', 'sequential', 'Administrivia_and_Circuit_Elements', None])) - private_location_no_draft = private_vertical.location._replace(revision=None) + private_location_no_draft = private_vertical.location.replace(revision=None) module_store.update_children(sequential.location, sequential.children + [private_location_no_draft.url()]) @@ -445,20 +445,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): self.verify_content_existence(module_store, root_dir, location, 'custom_tags', 'custom_tag_template') # check for graiding_policy.json - fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012') - self.assertTrue(fs.exists('grading_policy.json')) + filesystem = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012') + self.assertTrue(filesystem.exists('grading_policy.json')) course = module_store.get_item(location) # compare what's on disk compared to what we have in our course - with fs.open('grading_policy.json', 'r') as grading_policy: + with filesystem.open('grading_policy.json', 'r') as grading_policy: on_disk = loads(grading_policy.read()) self.assertEqual(on_disk, course.grading_policy) #check for policy.json - self.assertTrue(fs.exists('policy.json')) + self.assertTrue(filesystem.exists('policy.json')) # compare what's on disk to what we have in the course module - with fs.open('policy.json', 'r') as course_policy: + with filesystem.open('policy.json', 'r') as course_policy: on_disk = loads(course_policy.read()) self.assertIn('course/6.002_Spring_2012', on_disk) self.assertEqual(on_disk['course/6.002_Spring_2012'], own_metadata(course)) @@ -813,37 +813,37 @@ class ContentStoreTest(ModuleStoreTestCase): self.assertEqual(200, resp.status_code) # go look at a subsection page - subsection_location = loc._replace(category='sequential', name='test_sequence') + subsection_location = loc.replace(category='sequential', name='test_sequence') resp = self.client.get(reverse('edit_subsection', kwargs={'location': subsection_location.url()})) self.assertEqual(200, resp.status_code) # go look at the Edit page - unit_location = loc._replace(category='vertical', name='test_vertical') + unit_location = loc.replace(category='vertical', name='test_vertical') resp = self.client.get(reverse('edit_unit', kwargs={'location': unit_location.url()})) self.assertEqual(200, resp.status_code) # delete a component - del_loc = loc._replace(category='html', name='test_html') + del_loc = loc.replace(category='html', name='test_html') resp = self.client.post(reverse('delete_item'), json.dumps({'id': del_loc.url()}), "application/json") self.assertEqual(200, resp.status_code) # delete a unit - del_loc = loc._replace(category='vertical', name='test_vertical') + del_loc = loc.replace(category='vertical', name='test_vertical') resp = self.client.post(reverse('delete_item'), json.dumps({'id': del_loc.url()}), "application/json") self.assertEqual(200, resp.status_code) # delete a unit - del_loc = loc._replace(category='sequential', name='test_sequence') + del_loc = loc.replace(category='sequential', name='test_sequence') resp = self.client.post(reverse('delete_item'), json.dumps({'id': del_loc.url()}), "application/json") self.assertEqual(200, resp.status_code) # delete a chapter - del_loc = loc._replace(category='chapter', name='chapter_2') + del_loc = loc.replace(category='chapter', name='chapter_2') resp = self.client.post(reverse('delete_item'), json.dumps({'id': del_loc.url()}), "application/json") self.assertEqual(200, resp.status_code) diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index ae04e3aac4..33c7b61251 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -9,7 +9,7 @@ import re from collections import namedtuple from .exceptions import InvalidLocationError, InsufficientSpecificationError -from xmodule.errortracker import ErrorLog, make_error_tracker +from xmodule.errortracker import make_error_tracker from bson.son import SON log = logging.getLogger('mitx.' + 'modulestore') @@ -64,7 +64,6 @@ class Location(_LocationBase): """ return re.sub('_+', '_', invalid.sub('_', value)) - @staticmethod def clean(value): """ @@ -72,7 +71,6 @@ class Location(_LocationBase): """ return Location._clean(value, INVALID_CHARS) - @staticmethod def clean_keeping_underscores(value): """ @@ -82,7 +80,6 @@ class Location(_LocationBase): """ return INVALID_CHARS.sub('_', value) - @staticmethod def clean_for_url_name(value): """ @@ -154,9 +151,7 @@ class Location(_LocationBase): to mean wildcard selection. """ - - if (org is None and course is None and category is None and - name is None and revision is None): + if (org is None and course is None and category is None and name is None and revision is None): location = loc_or_tag else: location = (loc_or_tag, org, course, category, name, revision) @@ -191,7 +186,7 @@ class Location(_LocationBase): match = MISSING_SLASH_URL_RE.match(location) if match is None: log.debug('location is instance of %s but no URL match' % basestring) - raise InvalidLocationError(location) + raise InvalidLocationError(location) groups = match.groupdict() check_dict(groups) return _LocationBase.__new__(_cls, **groups) @@ -233,7 +228,7 @@ class Location(_LocationBase): html id attributes """ s = "-".join(str(v) for v in self.list() - if v is not None) + if v is not None) return Location.clean_for_html(s) def dict(self): @@ -258,6 +253,12 @@ class Location(_LocationBase): at the location URL hierachy""" return "/".join([self.org, self.course, self.name]) + def replace(self, **kwargs): + ''' + Expose a public method for replacing location elements + ''' + return self._replace(**kwargs) + class ModuleStore(object): """ @@ -382,12 +383,6 @@ class ModuleStore(object): ''' raise NotImplementedError - def get_course(self, course_id): - ''' - Look for a specific course id. Returns the course descriptor, or None if not found. - ''' - raise NotImplementedError - def get_parent_locations(self, location, course_id): '''Find all locations that are the parents of this location in this course. Needed for path_to_location(). @@ -406,8 +401,7 @@ class ModuleStore(object): courses = [ course for course in self.get_courses() - if course.location.org == location.org - and course.location.course == location.course + if course.location.org == location.org and course.location.course == location.course ] return courses diff --git a/common/lib/xmodule/xmodule/modulestore/draft.py b/common/lib/xmodule/xmodule/modulestore/draft.py index c3f1b23688..9262c5e9d6 100644 --- a/common/lib/xmodule/xmodule/modulestore/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/draft.py @@ -13,11 +13,12 @@ def as_draft(location): """ return Location(location)._replace(revision=DRAFT) + def as_published(location): """ Returns the Location that is the published version for `location` """ - return Location(location)._replace(revision=None) + return Location(location)._replace(revision=None) def wrap_draft(item): diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 31237af7b9..8cf148f742 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -3,7 +3,6 @@ from time import gmtime from uuid import uuid4 from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore -from xmodule.timeparse import stringify_time from xmodule.modulestore.inheritance import own_metadata From 4c80325db3eb2eacf1f259f1b42ba1bdcd2bd7ee Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 20 May 2013 08:11:54 -0400 Subject: [PATCH 16/57] Start working on COE editor, fix name checking in module_render --- .../xmodule/combined_open_ended_module.py | 6 + .../js/src/combinedopenended/edit.coffee | 299 ++++++++++++++++++ .../xmodule/xmodule/peer_grading_module.py | 3 + lms/djangoapps/courseware/module_render.py | 24 +- 4 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index f4074283fe..bffd4d4c78 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -221,3 +221,9 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): has_score = True always_recalculate_grades = True template_dir_name = "combinedopenended" + + #Specify whether or not to pass in S3 interface + needs_s3_interface = True + + #Specify whether or not to pass in open ended interface + needs_open_ended_interface = True diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee new file mode 100644 index 0000000000..b723f230e9 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee @@ -0,0 +1,299 @@ +class @MarkdownEditingDescriptor extends XModule.Descriptor + # TODO really, these templates should come from or also feed the cheatsheet + @multipleChoiceTemplate : "( ) incorrect\n( ) incorrect\n(x) correct\n" + @checkboxChoiceTemplate: "[x] correct\n[ ] incorrect\n[x] correct\n" + @stringInputTemplate: "= answer\n" + @numberInputTemplate: "= answer +- x%\n" + @selectTemplate: "[[incorrect, (correct), incorrect]]\n" + @headerTemplate: "Header\n=====\n" + @explanationTemplate: "[explanation]\nShort explanation\n[explanation]\n" + + constructor: (element) -> + @element = element + + if $(".markdown-box", @element).length != 0 + @markdown_editor = CodeMirror.fromTextArea($(".markdown-box", element)[0], { + lineWrapping: true + mode: null + }) + @setCurrentEditor(@markdown_editor) + # Add listeners for toolbar buttons (only present for markdown editor) + @element.on('click', '.xml-tab', @onShowXMLButton) + @element.on('click', '.format-buttons a', @onToolbarButton) + @element.on('click', '.cheatsheet-toggle', @toggleCheatsheet) + # Hide the XML text area + $(@element.find('.xml-box')).hide() + else + @createXMLEditor() + + ### + Creates the XML Editor and sets it as the current editor. If text is passed in, + it will replace the text present in the HTML template. + + text: optional argument to override the text passed in via the HTML template + ### + createXMLEditor: (text) -> + @xml_editor = CodeMirror.fromTextArea($(".xml-box", @element)[0], { + mode: "xml" + lineNumbers: true + lineWrapping: true + }) + if text + @xml_editor.setValue(text) + @setCurrentEditor(@xml_editor) + + ### + User has clicked to show the XML editor. Before XML editor is swapped in, + the user will need to confirm the one-way conversion. + ### + onShowXMLButton: (e) => + e.preventDefault(); + if @confirmConversionToXml() + @createXMLEditor(MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue())) + # Need to refresh to get line numbers to display properly (and put cursor position to 0) + @xml_editor.setCursor(0) + @xml_editor.refresh() + # Hide markdown-specific toolbar buttons + $(@element.find('.editor-bar')).hide() + + ### + Have the user confirm the one-way conversion to XML. + Returns true if the user clicked OK, else false. + ### + confirmConversionToXml: -> + # TODO: use something besides a JavaScript confirm dialog? + return confirm("If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?") + + ### + Event listener for toolbar buttons (only possible when markdown editor is visible). + ### + onToolbarButton: (e) => + e.preventDefault(); + selection = @markdown_editor.getSelection() + revisedSelection = null + switch $(e.currentTarget).attr('class') + when "multiple-choice-button" then revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection) + when "string-button" then revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection) + when "number-button" then revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection) + when "checks-button" then revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection) + when "dropdown-button" then revisedSelection = MarkdownEditingDescriptor.insertSelect(selection) + when "header-button" then revisedSelection = MarkdownEditingDescriptor.insertHeader(selection) + when "explanation-button" then revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection) + else # ignore click + + if revisedSelection != null + @markdown_editor.replaceSelection(revisedSelection) + @markdown_editor.focus() + + ### + Event listener for toggling cheatsheet (only possible when markdown editor is visible). + ### + toggleCheatsheet: (e) => + e.preventDefault(); + if !$(@markdown_editor.getWrapperElement()).find('.simple-editor-cheatsheet')[0] + @cheatsheet = $($('#simple-editor-cheatsheet').html()) + $(@markdown_editor.getWrapperElement()).append(@cheatsheet) + + setTimeout (=> @cheatsheet.toggleClass('shown')), 10 + + ### + Stores the current editor and hides the one that is not displayed. + ### + setCurrentEditor: (editor) -> + if @current_editor + $(@current_editor.getWrapperElement()).hide() + @current_editor = editor + $(@current_editor.getWrapperElement()).show() + $(@current_editor).focus(); + + ### + Called when save is called. Listeners are unregistered because editing the block again will + result in a new instance of the descriptor. Note that this is NOT the case for cancel-- + when cancel is called the instance of the descriptor is reused if edit is selected again. + ### + save: -> + @element.off('click', '.xml-tab', @changeEditor) + @element.off('click', '.format-buttons a', @onToolbarButton) + @element.off('click', '.cheatsheet-toggle', @toggleCheatsheet) + if @current_editor == @markdown_editor + { + data: MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue()) + metadata: + markdown: @markdown_editor.getValue() + } + else + { + data: @xml_editor.getValue() + metadata: + markdown: null + } + + @insertMultipleChoice: (selectedText) -> + return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '(', ')', MarkdownEditingDescriptor.multipleChoiceTemplate) + + @insertCheckboxChoice: (selectedText) -> + return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '[', ']', MarkdownEditingDescriptor.checkboxChoiceTemplate) + + @insertGenericChoice: (selectedText, choiceStart, choiceEnd, template) -> + if selectedText.length > 0 + # Replace adjacent newlines with a single newline, strip any trailing newline + cleanSelectedText = selectedText.replace(/\n+/g, '\n').replace(/\n$/,'') + lines = cleanSelectedText.split('\n') + revisedLines = '' + for line in lines + revisedLines += choiceStart + # a stand alone x before other text implies that this option is "correct" + if /^\s*x\s+(\S)/i.test(line) + # Remove the x and any initial whitespace as long as there's more text on the line + line = line.replace(/^\s*x\s+(\S)/i, '$1') + revisedLines += 'x' + else + revisedLines += ' ' + revisedLines += choiceEnd + ' ' + line + '\n' + return revisedLines + else + return template + + @insertStringInput: (selectedText) -> + return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.stringInputTemplate) + + @insertNumberInput: (selectedText) -> + return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.numberInputTemplate) + + @insertSelect: (selectedText) -> + return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[[', ']]', MarkdownEditingDescriptor.selectTemplate) + + @insertHeader: (selectedText) -> + return MarkdownEditingDescriptor.insertGenericInput(selectedText, '', '\n====\n', MarkdownEditingDescriptor.headerTemplate) + + @insertExplanation: (selectedText) -> + return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[explanation]\n', '\n[explanation]', MarkdownEditingDescriptor.explanationTemplate) + + @insertGenericInput: (selectedText, lineStart, lineEnd, template) -> + if selectedText.length > 0 + # TODO: should this insert a newline afterwards? + return lineStart + selectedText + lineEnd + else + return template + +# We may wish to add insertHeader. Here is Tom's code. +# function makeHeader() { +# var selection = simpleEditor.getSelection(); +# var revisedSelection = selection + '\n'; +# for(var i = 0; i < selection.length; i++) { +#revisedSelection += '='; +# } +# simpleEditor.replaceSelection(revisedSelection); +#} +# + @markdownToXml: (markdown)-> + toXml = `function(markdown) { + var xml = markdown; + + // replace headers + xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '

$1

'); + xml = xml.replace(/\n^\=\=+$/gm, ''); + + // group multiple choice answers + xml = xml.replace(/(^\s*\(.?\).*?$\n*)+/gm, function(match, p) { + var groupString = '\n'; + groupString += ' \n'; + var options = match.split('\n'); + for(var i = 0; i < options.length; i++) { + if(options[i].length > 0) { + var value = options[i].split(/^\s*\(.?\)\s*/)[1]; + var correct = /^\s*\(x\)/i.test(options[i]); + groupString += ' ' + value + '\n'; + } + } + groupString += ' \n'; + groupString += '\n\n'; + return groupString; + }); + + // group check answers + xml = xml.replace(/(^\s*\[.?\].*?$\n*)+/gm, function(match, p) { + var groupString = '\n'; + groupString += ' \n'; + var options = match.split('\n'); + for(var i = 0; i < options.length; i++) { + if(options[i].length > 0) { + var value = options[i].split(/^\s*\[.?\]\s*/)[1]; + var correct = /^\s*\[x\]/i.test(options[i]); + groupString += ' ' + value + '\n'; + } + } + groupString += ' \n'; + groupString += '\n\n'; + return groupString; + }); + + // replace string and numerical + xml = xml.replace(/^\=\s*(.*?$)/gm, function(match, p) { + var string; + var floatValue = parseFloat(p); + if(!isNaN(floatValue)) { + var params = /(.*?)\+\-\s*(.*?$)/.exec(p); + if(params) { + string = '\n'; + string += ' \n'; + } else { + string = '\n'; + } + string += ' \n'; + string += '\n\n'; + } else { + string = '\n \n\n\n'; + } + return string; + }); + + // replace selects + xml = xml.replace(/\[\[(.+?)\]\]/g, function(match, p) { + var selectString = '\n\n'; + selectString += ' \n'; + return selectString; + }); + + // split scripts and wrap paragraphs + var splits = xml.split(/(\<\/?script.*?\>)/g); + var scriptFlag = false; + for(var i = 0; i < splits.length; i++) { + if(/\ diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index bffd4d4c78..008cf5acb0 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -69,6 +69,7 @@ class CombinedOpenEndedFields(object): version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings) data = String(help="XML data for the problem", scope=Scope.content) weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings) + markdown = String(help="Markdown source of this module", scope=Scope.settings) class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule): @@ -213,9 +214,8 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): """ Module for adding combined open ended questions """ - mako_template = "widgets/raw-edit.html" + mako_template = "widgets/open-ended-edit.html" module_class = CombinedOpenEndedModule - filename_extension = "xml" stores_state = True has_score = True @@ -227,3 +227,23 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): #Specify whether or not to pass in open ended interface needs_open_ended_interface = True + + metadata_attributes = RawDescriptor.metadata_attributes + + js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/edit.coffee')]} + js_module_name = "OpenEndedMarkdownEditingDescriptor" + css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/combinedopenended/edit.scss')]} + + def get_context(self): + _context = RawDescriptor.get_context(self) + _context.update({'markdown': self.markdown, + 'enable_markdown': self.markdown is not None}) + return _context + + @property + def non_editable_metadata_fields(self): + non_editable_fields = super(CombinedOpenEndedDescriptor, self).non_editable_metadata_fields + non_editable_fields.extend([CombinedOpenEndedDescriptor.due, CombinedOpenEndedDescriptor.graceperiod, + CombinedOpenEndedDescriptor.markdown]) + return non_editable_fields + diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss b/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss new file mode 100644 index 0000000000..1e4b300c28 --- /dev/null +++ b/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss @@ -0,0 +1,122 @@ +.editor-bar { + + .editor-tabs { + + .advanced-toggle { + @include white-button; + height: auto; + margin-top: -1px; + padding: 3px 9px; + font-size: 12px; + + &.current { + border: 1px solid $lightGrey !important; + border-radius: 3px !important; + background: $lightGrey !important; + color: $darkGrey !important; + pointer-events: none; + cursor: none; + + &:hover { + box-shadow: 0 0 0 0 !important; + } + } + } + + .cheatsheet-toggle { + width: 21px; + height: 21px; + padding: 0; + margin: 0 5px 0 15px; + border-radius: 22px; + border: 1px solid #a5aaaf; + background: #e5ecf3; + font-size: 13px; + font-weight: 700; + color: #565d64; + text-align: center; + } + } +} + +.simple-editor-cheatsheet { + position: absolute; + top: 0; + left: 100%; + width: 0; + border-radius: 0 3px 3px 0; + @include linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 4px); + background-color: #fff; + overflow: hidden; + @include transition(width .3s); + + &.shown { + width: 300px; + height: 100%; + overflow-y: scroll; + } + + .cheatsheet-wrapper { + width: 240px; + padding: 20px 30px; + } + + h6 { + margin-bottom: 7px; + font-size: 15px; + font-weight: 700; + } + + .row { + @include clearfix; + padding-bottom: 5px !important; + margin-bottom: 10px !important; + border-bottom: 1px solid #ddd !important; + + &:last-child { + border-bottom: none !important; + margin-bottom: 0 !important; + } + } + + .col { + float: left; + + &.sample { + width: 60px; + margin-right: 30px; + } + } + + pre { + font-size: 12px; + line-height: 18px; + } + + code { + padding: 0; + background: none; + } +} + +.combinedopenended-editor-icon { + display: inline-block; + width: 26px; + height: 21px; + vertical-align: middle; + background: url(../img/problem-editor-icons.png) no-repeat; +} + +.combinedopenended-editor-icon.multiple-choice { + background-position: 0 0; +} + +.combinedopenended-editor-icon.explanation { + width: 28px; + background-position: -111px 0; +} + +.combinedopenended-editor-icon.dropdown { + width: 28px; + background-position: -111px 0; +} \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee index b723f230e9..58697aff07 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee @@ -1,4 +1,4 @@ -class @MarkdownEditingDescriptor extends XModule.Descriptor +class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor # TODO really, these templates should come from or also feed the cheatsheet @multipleChoiceTemplate : "( ) incorrect\n( ) incorrect\n(x) correct\n" @checkboxChoiceTemplate: "[x] correct\n[ ] incorrect\n[x] correct\n" diff --git a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml index a11367b46f..cfbb5b7334 100644 --- a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml +++ b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml @@ -7,6 +7,7 @@ metadata: skip_spelling_checks: False accept_file_upload: False weight: "" + markdown: "" data: | From 3a15f935646508eba952a306c8551ef0e589a8ce Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 20 May 2013 09:20:46 -0400 Subject: [PATCH 18/57] Render proper templates when user clicks on editor buttons --- .../js/src/combinedopenended/edit.coffee | 76 ++++--------------- .../templates/combinedopenended/default.yaml | 29 ------- 2 files changed, 13 insertions(+), 92 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee index 58697aff07..bccac80570 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee @@ -1,12 +1,8 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor # TODO really, these templates should come from or also feed the cheatsheet - @multipleChoiceTemplate : "( ) incorrect\n( ) incorrect\n(x) correct\n" - @checkboxChoiceTemplate: "[x] correct\n[ ] incorrect\n[x] correct\n" - @stringInputTemplate: "= answer\n" - @numberInputTemplate: "= answer +- x%\n" - @selectTemplate: "[[incorrect, (correct), incorrect]]\n" - @headerTemplate: "Header\n=====\n" - @explanationTemplate: "[explanation]\nShort explanation\n[explanation]\n" + @rubricTemplate : "+ Color Identification\n- Incorrect\n- Correct\n + Grammar\n- Poor\n- Acceptable\n- Superb \n" + @tasksTemplate: "[[Self, {0,1}AI, {1,3}Peer]]\n" + @promptTemplate: "[prompt]\nWhy is the sky blue?\n[prompt]\n" constructor: (element) -> @element = element @@ -49,7 +45,7 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor onShowXMLButton: (e) => e.preventDefault(); if @confirmConversionToXml() - @createXMLEditor(MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue())) + @createXMLEditor(OpenEndedMarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue())) # Need to refresh to get line numbers to display properly (and put cursor position to 0) @xml_editor.setCursor(0) @xml_editor.refresh() @@ -72,13 +68,9 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor selection = @markdown_editor.getSelection() revisedSelection = null switch $(e.currentTarget).attr('class') - when "multiple-choice-button" then revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection) - when "string-button" then revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection) - when "number-button" then revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection) - when "checks-button" then revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection) - when "dropdown-button" then revisedSelection = MarkdownEditingDescriptor.insertSelect(selection) - when "header-button" then revisedSelection = MarkdownEditingDescriptor.insertHeader(selection) - when "explanation-button" then revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection) + when "rubric-button" then revisedSelection = OpenEndedMarkdownEditingDescriptor.insertRubric(selection) + when "prompt-button" then revisedSelection = OpenEndedMarkdownEditingDescriptor.insertPrompt(selection) + when "tasks-button" then revisedSelection = OpenEndedMarkdownEditingDescriptor.insertTasks(selection) else # ignore click if revisedSelection != null @@ -128,46 +120,14 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor markdown: null } - @insertMultipleChoice: (selectedText) -> - return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '(', ')', MarkdownEditingDescriptor.multipleChoiceTemplate) + @insertRubric: (selectedText) -> + return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '(', ')', OpenEndedMarkdownEditingDescriptor.rubricTemplate) - @insertCheckboxChoice: (selectedText) -> - return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '[', ']', MarkdownEditingDescriptor.checkboxChoiceTemplate) + @insertPrompt: (selectedText) -> + return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[', ']', OpenEndedMarkdownEditingDescriptor.promptTemplate) - @insertGenericChoice: (selectedText, choiceStart, choiceEnd, template) -> - if selectedText.length > 0 - # Replace adjacent newlines with a single newline, strip any trailing newline - cleanSelectedText = selectedText.replace(/\n+/g, '\n').replace(/\n$/,'') - lines = cleanSelectedText.split('\n') - revisedLines = '' - for line in lines - revisedLines += choiceStart - # a stand alone x before other text implies that this option is "correct" - if /^\s*x\s+(\S)/i.test(line) - # Remove the x and any initial whitespace as long as there's more text on the line - line = line.replace(/^\s*x\s+(\S)/i, '$1') - revisedLines += 'x' - else - revisedLines += ' ' - revisedLines += choiceEnd + ' ' + line + '\n' - return revisedLines - else - return template - - @insertStringInput: (selectedText) -> - return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.stringInputTemplate) - - @insertNumberInput: (selectedText) -> - return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', MarkdownEditingDescriptor.numberInputTemplate) - - @insertSelect: (selectedText) -> - return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[[', ']]', MarkdownEditingDescriptor.selectTemplate) - - @insertHeader: (selectedText) -> - return MarkdownEditingDescriptor.insertGenericInput(selectedText, '', '\n====\n', MarkdownEditingDescriptor.headerTemplate) - - @insertExplanation: (selectedText) -> - return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[explanation]\n', '\n[explanation]', MarkdownEditingDescriptor.explanationTemplate) + @insertTasks: (selectedText) -> + return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', OpenEndedMarkdownEditingDescriptor.tasksTemplate) @insertGenericInput: (selectedText, lineStart, lineEnd, template) -> if selectedText.length > 0 @@ -176,16 +136,6 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor else return template -# We may wish to add insertHeader. Here is Tom's code. -# function makeHeader() { -# var selection = simpleEditor.getSelection(); -# var revisedSelection = selection + '\n'; -# for(var i = 0; i < selection.length; i++) { -#revisedSelection += '='; -# } -# simpleEditor.replaceSelection(revisedSelection); -#} -# @markdownToXml: (markdown)-> toXml = `function(markdown) { var xml = markdown; diff --git a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml index cfbb5b7334..5bf8552997 100644 --- a/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml +++ b/common/lib/xmodule/xmodule/templates/combinedopenended/default.yaml @@ -10,35 +10,6 @@ metadata: markdown: "" data: | - - - - Category 1 - - - - - - -

Why is the sky blue?

-
- - - - - - - Enter essay here. - This is the answer. - {"grader_settings" : "peer_grading.conf", "problem_id" : "700x/Demo"} - - -
- children: [] From 986999c62edecbe28e51a68dec3c59329e46d032 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 20 May 2013 09:50:15 -0400 Subject: [PATCH 19/57] Fix cheatsheet, start working on xml replacement --- cms/templates/widgets/open-ended-edit.html | 18 +++-- .../js/src/combinedopenended/edit.coffee | 71 +++++++------------ 2 files changed, 38 insertions(+), 51 deletions(-) diff --git a/cms/templates/widgets/open-ended-edit.html b/cms/templates/widgets/open-ended-edit.html index b59e6f7068..f6aababa8c 100644 --- a/cms/templates/widgets/open-ended-edit.html +++ b/cms/templates/widgets/open-ended-edit.html @@ -41,7 +41,7 @@
-
[explanation] A short explanation of the answer. [explanation]
+
[prompt]Why is the sky blue?[prompt]
@@ -49,9 +49,17 @@
-
( ) red
-                    ( ) green
-                    (x) blue
+

+                    [rubric]
+                    + Color Identification
+                    - Incorrect
+                    - Correct
+                    + Grammar
+                    - Poor
+                    - Acceptable
+                    - Superb
+                    [rubric]
+                
@@ -59,7 +67,7 @@
-
[[wrong, (right)]]
+
[tasks](Self), ({0,1}AI), ({1,3}Peer)[tasks]
diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee index bccac80570..31944a0217 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee @@ -1,7 +1,7 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor # TODO really, these templates should come from or also feed the cheatsheet - @rubricTemplate : "+ Color Identification\n- Incorrect\n- Correct\n + Grammar\n- Poor\n- Acceptable\n- Superb \n" - @tasksTemplate: "[[Self, {0,1}AI, {1,3}Peer]]\n" + @rubricTemplate : "[rubric]\n+ Color Identification\n- Incorrect\n- Correct\n + Grammar\n- Poor\n- Acceptable\n- Superb \n[rubric]\n" + @tasksTemplate: "[tasks]\n(Self), ({0,1}AI), ({1,3}Peer)\n[tasks]\n" @promptTemplate: "[prompt]\nWhy is the sky blue?\n[prompt]\n" constructor: (element) -> @@ -108,26 +108,26 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor @element.off('click', '.format-buttons a', @onToolbarButton) @element.off('click', '.cheatsheet-toggle', @toggleCheatsheet) if @current_editor == @markdown_editor - { - data: MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue()) - metadata: - markdown: @markdown_editor.getValue() - } + { + data: MarkdownEditingDescriptor.markdownToXml(@markdown_editor.getValue()) + metadata: + markdown: @markdown_editor.getValue() + } else - { - data: @xml_editor.getValue() - metadata: - markdown: null - } + { + data: @xml_editor.getValue() + metadata: + markdown: null + } @insertRubric: (selectedText) -> - return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '(', ')', OpenEndedMarkdownEditingDescriptor.rubricTemplate) + return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[rubric]', '[rubric]', OpenEndedMarkdownEditingDescriptor.rubricTemplate) @insertPrompt: (selectedText) -> - return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[', ']', OpenEndedMarkdownEditingDescriptor.promptTemplate) + return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[prompt]', '[prompt]', OpenEndedMarkdownEditingDescriptor.promptTemplate) @insertTasks: (selectedText) -> - return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '', OpenEndedMarkdownEditingDescriptor.tasksTemplate) + return OpenEndedMarkdownEditingDescriptor.insertGenericInput(selectedText, '[tasks]', '[tasks]', OpenEndedMarkdownEditingDescriptor.tasksTemplate) @insertGenericInput: (selectedText, lineStart, lineEnd, template) -> if selectedText.length > 0 @@ -140,12 +140,8 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor toXml = `function(markdown) { var xml = markdown; - // replace headers - xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '

$1

'); - xml = xml.replace(/\n^\=\=+$/gm, ''); - - // group multiple choice answers - xml = xml.replace(/(^\s*\(.?\).*?$\n*)+/gm, function(match, p) { + // group rubrics + xml = xml.replace(/\[rubric\]\n?([^\]]*)\[\/?rubric\]/gmi, function(match, p) { var groupString = '\n'; groupString += ' \n'; var options = match.split('\n'); @@ -213,37 +209,20 @@ class @OpenEndedMarkdownEditingDescriptor extends XModule.Descriptor selectString += '
\n\n'; return selectString; }); - - // replace explanations - xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) { - var selectString = '\n
\nExplanation\n\n' + p1 + '\n
\n
'; + + // replace prompts + xml = xml.replace(/\[prompt\]\n?([^\]]*)\[\/?prompt\]/gmi, function(match, p1) { + var selectString = '\n' + p1 + '\n'; return selectString; }); - // split scripts and wrap paragraphs - var splits = xml.split(/(\<\/?script.*?\>)/g); - var scriptFlag = false; - for(var i = 0; i < splits.length; i++) { - if(/\ + - - - - - - - - - - - - - - - - - - <% for src in js_source %> - - <% end %> - - - <% for src in js_specs %> - - <% end %> - - - - - - - - diff --git a/common/templates/jasmine/base.html b/common/templates/jasmine/base.html index 9a1b3bed92..0133edadfa 100644 --- a/common/templates/jasmine/base.html +++ b/common/templates/jasmine/base.html @@ -12,6 +12,7 @@ + {% load compressed %} {# static files #} @@ -37,15 +38,14 @@ + @@ -44,30 +45,10 @@ diff --git a/common/test/phantom-jasmine b/common/test/phantom-jasmine deleted file mode 160000 index a54d435b55..0000000000 --- a/common/test/phantom-jasmine +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a54d435b5556650efbcdb0490e6c7928ac75238a diff --git a/jenkins/test.sh b/jenkins/test.sh index d8cd2c1843..e8b032f195 100755 --- a/jenkins/test.sh +++ b/jenkins/test.sh @@ -82,7 +82,7 @@ rake test_common/lib/xmodule || TESTS_FAILED=1 rake phantomjs_jasmine_lms || TESTS_FAILED=1 rake phantomjs_jasmine_cms || TESTS_FAILED=1 rake phantomjs_jasmine_common/lib/xmodule || TESTS_FAILED=1 -rake phantomjs_jasmine_discussion || TESTS_FAILED=1 +rake phantomjs_jasmine_common/static/coffee || TESTS_FAILED=1 rake coverage:xml coverage:html diff --git a/lms/envs/jasmine.py b/lms/envs/jasmine.py index 2c30bc7de7..4a78ed8075 100644 --- a/lms/envs/jasmine.py +++ b/lms/envs/jasmine.py @@ -36,7 +36,12 @@ PIPELINE_JS['spec'] = { } JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' +JASMINE_REPORT_DIR = os.environ.get('JASMINE_REPORT_DIR', 'reports/lms/jasmine') + +TEMPLATE_CONTEXT_PROCESSORS += ('settings_context_processor.context_processors.settings',) +TEMPLATE_VISIBLE_SETTINGS = ('JASMINE_REPORT_DIR', ) STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib') +STATICFILES_DIRS.append(REPO_ROOT/'node_modules/jasmine-reporters/src') -INSTALLED_APPS += ('django_jasmine', ) +INSTALLED_APPS += ('django_jasmine', 'settings_context_processor') diff --git a/package.json b/package.json index 7fa287018a..2dd67d5be4 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "dependencies": { "coffee-script": "1.6.X", - "phantom-jasmine": "0.1.0" + "phantom-jasmine": "0.1.0", + "jasmine-reporters": "0.2.1" } } diff --git a/rakefiles/jasmine.rake b/rakefiles/jasmine.rake index 1e5050801e..4182bef9e2 100644 --- a/rakefiles/jasmine.rake +++ b/rakefiles/jasmine.rake @@ -48,6 +48,7 @@ def template_jasmine_runner(lib) sh("node_modules/.bin/coffee -c #{coffee_files.join(' ')}") end phantom_jasmine_path = File.expand_path("node_modules/phantom-jasmine") + jasmine_reporters_path = File.expand_path("node_modules/jasmine-reporters") common_js_root = File.expand_path("common/static/js") common_coffee_root = File.expand_path("common/static/coffee/src") @@ -58,6 +59,7 @@ def template_jasmine_runner(lib) js_specs = Dir[spec_glob].sort_by {|p| [p.split('/').length, p]} .map {|f| File.expand_path(f)} js_source = Dir[src_glob].sort_by {|p| [p.split('/').length, p]} .map {|f| File.expand_path(f)} + report_dir = report_dir_path("#{lib}/jasmine") template = ERB.new(File.read("common/templates/jasmine/jasmine_test_runner.html.erb")) template_output = "#{lib}/jasmine_test_runner.html" File.open(template_output, 'w') do |f| @@ -66,6 +68,11 @@ def template_jasmine_runner(lib) yield File.expand_path(template_output) end +def run_phantom_js(url) + phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs' + sh("#{phantomjs} node_modules/jasmine-reporters/test/phantomjs-testrunner.js #{url}") +end + [:lms, :cms].each do |system| desc "Open jasmine tests for #{system} in your default browser" task "browse_jasmine_#{system}" => :assets do @@ -78,14 +85,16 @@ end desc "Use phantomjs to run jasmine tests for #{system} from the console" task "phantomjs_jasmine_#{system}" => :assets do - phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs' django_for_jasmine(system, false) do |jasmine_url| - sh("#{phantomjs} node_modules/phantom-jasmine/lib/run_jasmine_test.coffee #{jasmine_url}") + run_phantom_js(jasmine_url) end end end -Dir["common/lib/*"].select{|lib| File.directory?(lib)}.each do |lib| +STATIC_JASMINE_TESTS = Dir["common/lib/*"].select{|lib| File.directory?(lib)} +STATIC_JASMINE_TESTS << 'common/static/coffee' + +STATIC_JASMINE_TESTS.each do |lib| desc "Open jasmine tests for #{lib} in your default browser" task "browse_jasmine_#{lib}" do template_jasmine_runner(lib) do |f| @@ -97,26 +106,14 @@ Dir["common/lib/*"].select{|lib| File.directory?(lib)}.each do |lib| desc "Use phantomjs to run jasmine tests for #{lib} from the console" task "phantomjs_jasmine_#{lib}" do - phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs' template_jasmine_runner(lib) do |f| - sh("#{phantomjs} node_modules/phantom-jasmine/lib/run_jasmine_test.coffee #{f}") + run_phantom_js(f) end end end desc "Open jasmine tests for discussion in your default browser" -task "browse_jasmine_discussion" do - template_jasmine_runner("common/static/coffee") do |f| - sh("python -m webbrowser -t 'file://#{f}'") - puts "Press ENTER to terminate".red - $stdin.gets - end -end +task "browse_jasmine_discussion" => "browse_jasmine_common/static/coffee" desc "Use phantomjs to run jasmine tests for discussion from the console" -task "phantomjs_jasmine_discussion" do - phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs' - template_jasmine_runner("common/static/coffee") do |f| - sh("#{phantomjs} node_modules/phantom-jasmine/lib/run_jasmine_test.coffee #{f}") - end -end +task "phantomjs_jasmine_discussion" => "phantomjs_jasmine_common/static/coffee" diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 3d8b95f8e2..69ee484a94 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -82,3 +82,5 @@ django_nose==1.1 django-jasmine==0.3.2 django_debug_toolbar django-debug-toolbar-mongo + +git+https://github.com/mfogel/django-settings-context-processor.git From f521b8e8c010b98941279f8c5828627cc46334e4 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 20 May 2013 14:48:48 -0400 Subject: [PATCH 43/57] Handle errors while sending emails around changing user data The most common error is Amazon SES having blacklisted an email address. However, we handle any error being raised during user.email_user. Fixes JIRA LMS-131 --- common/djangoapps/student/tests/factories.py | 20 +- common/djangoapps/student/tests/test_email.py | 261 ++++++++++++++++++ common/djangoapps/student/views.py | 86 ++++-- lms/templates/email_change_failed.html | 3 + pylintrc | 4 +- requirements/edx/base.txt | 2 +- 6 files changed, 343 insertions(+), 33 deletions(-) create mode 100644 common/djangoapps/student/tests/test_email.py create mode 100644 lms/templates/email_change_failed.html diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index d34f554fbf..d73bb6f01d 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -1,10 +1,14 @@ from student.models import (User, UserProfile, Registration, - CourseEnrollmentAllowed, CourseEnrollment) + CourseEnrollmentAllowed, CourseEnrollment, + PendingEmailChange) from django.contrib.auth.models import Group from datetime import datetime from factory import DjangoModelFactory, SubFactory, PostGenerationMethodCall, post_generation, Sequence from uuid import uuid4 +# Factories don't have __init__ methods, and are self documenting +# pylint: disable=W0232 + class GroupFactory(DjangoModelFactory): FACTORY_FOR = Group @@ -72,3 +76,17 @@ class CourseEnrollmentAllowedFactory(DjangoModelFactory): email = 'test@edx.org' course_id = 'edX/test/2012_Fall' + + +class PendingEmailChangeFactory(DjangoModelFactory): + """Factory for PendingEmailChange objects + + user: generated by UserFactory + new_email: sequence of new+email+{}@edx.org + activation_key: sequence of integers, padded to 30 characters + """ + FACTORY_FOR = PendingEmailChange + + user = SubFactory(UserFactory) + new_email = Sequence(u'new+email+{0}@edx.org'.format) + activation_key = Sequence(u'{:0<30d}'.format) diff --git a/common/djangoapps/student/tests/test_email.py b/common/djangoapps/student/tests/test_email.py new file mode 100644 index 0000000000..3b31bb5c28 --- /dev/null +++ b/common/djangoapps/student/tests/test_email.py @@ -0,0 +1,261 @@ +import json +import django.db + +from student.tests.factories import UserFactory, RegistrationFactory, PendingEmailChangeFactory +from student.views import reactivation_email_for_user, change_email_request, confirm_email_change +from student.models import UserProfile, PendingEmailChange +from django.contrib.auth.models import User +from django.test import TestCase, TransactionTestCase +from django.test.client import RequestFactory +from mock import Mock, patch +from django.http import Http404, HttpResponse +from django.conf import settings +from nose.plugins.skip import SkipTest + + +class TestException(Exception): + """Exception used for testing that nothing will catch explicitly""" + pass + + +def mock_render_to_string(template_name, context): + """Return a string that encodes template_name and context""" + return str((template_name, sorted(context.iteritems()))) + + +def mock_render_to_response(template_name, context): + """Return an HttpResponse with content that encodes template_name and context""" + return HttpResponse(mock_render_to_string(template_name, context)) + + +class EmailTestMixin(object): + """Adds useful assertions for testing `email_user`""" + + def assertEmailUser(self, email_user, subject_template, subject_context, body_template, body_context): + """Assert that `email_user` was used to send and email with the supplied subject and body + + `email_user`: The mock `django.contrib.auth.models.User.email_user` function + to verify + `subject_template`: The template to have been used for the subject + `subject_context`: The context to have been used for the subject + `body_template`: The template to have been used for the body + `body_context`: The context to have been used for the body + """ + email_user.assert_called_with( + mock_render_to_string(subject_template, subject_context), + mock_render_to_string(body_template, body_context), + settings.DEFAULT_FROM_EMAIL + ) + + +@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True)) +@patch('django.contrib.auth.models.User.email_user') +class ReactivationEmailTests(EmailTestMixin, TestCase): + """Test sending a reactivation email to a user""" + + def setUp(self): + self.user = UserFactory.create() + self.registration = RegistrationFactory.create(user=self.user) + + def reactivation_email(self): + """Send the reactivation email, and return the response as json data""" + return json.loads(reactivation_email_for_user(self.user).content) + + def assertReactivateEmailSent(self, email_user): + """Assert that the correct reactivation email has been sent""" + context = { + 'name': self.user.profile.name, + 'key': self.registration.activation_key + } + + self.assertEmailUser( + email_user, + 'emails/activation_email_subject.txt', + context, + 'emails/activation_email.txt', + context + ) + + def test_reactivation_email_failure(self, email_user): + self.user.email_user.side_effect = Exception + response_data = self.reactivation_email() + + self.assertReactivateEmailSent(email_user) + self.assertFalse(response_data['success']) + + def test_reactivation_email_success(self, email_user): + response_data = self.reactivation_email() + + self.assertReactivateEmailSent(email_user) + self.assertTrue(response_data['success']) + + +class EmailChangeRequestTests(TestCase): + """Test changing a user's email address""" + + def setUp(self): + self.user = UserFactory.create() + self.new_email = 'new.email@edx.org' + self.req_factory = RequestFactory() + self.request = self.req_factory.post('unused_url', data={ + 'password': 'test', + 'new_email': self.new_email + }) + self.request.user = self.user + self.user.email_user = Mock() + + def run_request(self, request=None): + """Execute request and return result parsed as json + + If request isn't passed in, use self.request instead + """ + if request is None: + request = self.request + + response = change_email_request(self.request) + return json.loads(response.content) + + def assertFailedRequest(self, response_data, expected_error): + """Assert that `response_data` indicates a failed request that returns `expected_error`""" + self.assertFalse(response_data['success']) + self.assertEquals(expected_error, response_data['error']) + self.assertFalse(self.user.email_user.called) + + def test_unauthenticated(self): + self.user.is_authenticated = False + with self.assertRaises(Http404): + change_email_request(self.request) + self.assertFalse(self.user.email_user.called) + + def test_invalid_password(self): + self.request.POST['password'] = 'wrong' + self.assertFailedRequest(self.run_request(), 'Invalid password') + + def test_invalid_emails(self): + for email in ('bad_email', 'bad_email@', '@bad_email'): + self.request.POST['new_email'] = email + self.assertFailedRequest(self.run_request(), 'Valid e-mail address required.') + + def check_duplicate_email(self, email): + """Test that a request to change a users email to `email` fails""" + request = self.req_factory.post('unused_url', data={ + 'new_email': email, + 'password': 'test', + }) + request.user = self.user + self.assertFailedRequest(self.run_request(request), 'An account with this e-mail already exists.') + + def test_duplicate_email(self): + UserFactory.create(email=self.new_email) + self.check_duplicate_email(self.new_email) + + def test_capitalized_duplicate_email(self): + raise SkipTest("We currently don't check for emails in a case insensitive way, but we should") + UserFactory.create(email=self.new_email) + self.check_duplicate_email(self.new_email.capitalize()) + + # TODO: Finish testing the rest of change_email_request + + +@patch('django.contrib.auth.models.User.email_user') +@patch('student.views.render_to_response', Mock(side_effect=mock_render_to_response, autospec=True)) +@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True)) +class EmailChangeConfirmationTests(EmailTestMixin, TransactionTestCase): + """Test that confirmation of email change requests function even in the face of exceptions thrown while sending email""" + def setUp(self): + self.user = UserFactory.create() + self.profile = UserProfile.objects.get(user=self.user) + self.req_factory = RequestFactory() + self.request = self.req_factory.get('unused_url') + self.request.user = self.user + self.user.email_user = Mock() + self.pending_change_request = PendingEmailChangeFactory.create(user=self.user) + self.key = self.pending_change_request.activation_key + + def assertRolledBack(self): + """Assert that no changes to user, profile, or pending email have been made to the db""" + self.assertEquals(self.user.email, User.objects.get(username=self.user.username).email) + self.assertEquals(self.profile.meta, UserProfile.objects.get(user=self.user).meta) + self.assertEquals(1, PendingEmailChange.objects.count()) + + def assertFailedBeforeEmailing(self, email_user): + """Assert that the function failed before emailing a user""" + self.assertRolledBack() + self.assertFalse(email_user.called) + + def check_confirm_email_change(self, expected_template, expected_context): + """Call `confirm_email_change` and assert that the content was generated as expected + + `expected_template`: The name of the template that should have been used + to generate the content + `expected_context`: The context dictionary that should have been used to + generate the content + """ + response = confirm_email_change(self.request, self.key) + self.assertEquals( + mock_render_to_response(expected_template, expected_context).content, + response.content + ) + + def assertChangeEmailSent(self, email_user): + """Assert that the correct email was sent to confirm an email change""" + context = { + 'old_email': self.user.email, + 'new_email': self.pending_change_request.new_email, + } + self.assertEmailUser( + email_user, + 'emails/email_change_subject.txt', + context, + 'emails/confirm_email_change.txt', + context + ) + + def test_not_pending(self, email_user): + self.key = 'not_a_key' + self.check_confirm_email_change('invalid_email_key.html', {}) + self.assertFailedBeforeEmailing(email_user) + + def test_duplicate_email(self, email_user): + UserFactory.create(email=self.pending_change_request.new_email) + self.check_confirm_email_change('email_exists.html', {}) + self.assertFailedBeforeEmailing(email_user) + + def test_old_email_fails(self, email_user): + email_user.side_effect = [Exception, None] + self.check_confirm_email_change('email_change_failed.html', { + 'email': self.user.email, + }) + self.assertRolledBack() + self.assertChangeEmailSent(email_user) + + def test_new_email_fails(self, email_user): + email_user.side_effect = [None, Exception] + self.check_confirm_email_change('email_change_failed.html', { + 'email': self.pending_change_request.new_email + }) + self.assertRolledBack() + self.assertChangeEmailSent(email_user) + + def test_successful_email_change(self, email_user): + self.check_confirm_email_change('email_change_successful.html', { + 'old_email': self.user.email, + 'new_email': self.pending_change_request.new_email + }) + self.assertChangeEmailSent(email_user) + meta = json.loads(UserProfile.objects.get(user=self.user).meta) + self.assertIn('old_emails', meta) + self.assertEquals(self.user.email, meta['old_emails'][0][0]) + self.assertEquals( + self.pending_change_request.new_email, + User.objects.get(username=self.user.username).email + ) + self.assertEquals(0, PendingEmailChange.objects.count()) + + @patch('student.views.PendingEmailChange.objects.get', Mock(side_effect=TestException)) + @patch('student.views.transaction.rollback', wraps=django.db.transaction.rollback) + def test_always_rollback(self, rollback, _email_user): + with self.assertRaises(TestException): + confirm_email_change(self.request, self.key) + + rollback.assert_called_with() diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index e8a70d6089..8059026e12 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -19,7 +19,7 @@ from django.core.context_processors import csrf from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.core.validators import validate_email, validate_slug, ValidationError -from django.db import IntegrityError +from django.db import IntegrityError, transaction from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseRedirect, Http404 from django.shortcuts import redirect from django_future.csrf import ensure_csrf_cookie, csrf_exempt @@ -655,7 +655,7 @@ def create_account(request, post_override=None): elif not settings.GENERATE_RANDOM_USER_CREDENTIALS: res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) except: - log.exception(sys.exc_info()) + log.warning('Unable to send activation email to user', exc_info=True) js['value'] = 'Could not send activation e-mail.' return HttpResponse(json.dumps(js)) @@ -975,7 +975,11 @@ def reactivation_email_for_user(user): subject = ''.join(subject.splitlines()) message = render_to_string('emails/activation_email.txt', d) - res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + try: + res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + except: + log.warning('Unable to send reactivation email', exc_info=True) + return HttpResponse(json.dumps({'success': False, 'error': 'Unable to send reactivation email'})) return HttpResponse(json.dumps({'success': True})) @@ -1001,7 +1005,7 @@ def change_email_request(request): return HttpResponse(json.dumps({'success': False, 'error': 'Valid e-mail address required.'})) - if len(User.objects.filter(email=new_email)) != 0: + if User.objects.filter(email=new_email).count() != 0: ## CRITICAL TODO: Handle case sensitivity for e-mails return HttpResponse(json.dumps({'success': False, 'error': 'An account with this e-mail already exists.'})) @@ -1036,41 +1040,63 @@ def change_email_request(request): @ensure_csrf_cookie +@transaction.commit_manually def confirm_email_change(request, key): ''' User requested a new e-mail. This is called when the activation link is clicked. We confirm with the old e-mail, and update ''' try: - pec = PendingEmailChange.objects.get(activation_key=key) - except PendingEmailChange.DoesNotExist: - return render_to_response("invalid_email_key.html", {}) + try: + pec = PendingEmailChange.objects.get(activation_key=key) + except PendingEmailChange.DoesNotExist: + transaction.rollback() + return render_to_response("invalid_email_key.html", {}) - user = pec.user - d = {'old_email': user.email, - 'new_email': pec.new_email} + user = pec.user + address_context = { + 'old_email': user.email, + 'new_email': pec.new_email + } - if len(User.objects.filter(email=pec.new_email)) != 0: - return render_to_response("email_exists.html", d) + if len(User.objects.filter(email=pec.new_email)) != 0: + transaction.rollback() + return render_to_response("email_exists.html", {}) - subject = render_to_string('emails/email_change_subject.txt', d) - subject = ''.join(subject.splitlines()) - message = render_to_string('emails/confirm_email_change.txt', d) - up = UserProfile.objects.get(user=user) - meta = up.get_meta() - if 'old_emails' not in meta: - meta['old_emails'] = [] - meta['old_emails'].append([user.email, datetime.datetime.now().isoformat()]) - up.set_meta(meta) - up.save() - # Send it to the old email... - user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) - user.email = pec.new_email - user.save() - pec.delete() - # And send it to the new email... - user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + subject = render_to_string('emails/email_change_subject.txt', address_context) + subject = ''.join(subject.splitlines()) + message = render_to_string('emails/confirm_email_change.txt', address_context) + up = UserProfile.objects.get(user=user) + meta = up.get_meta() + if 'old_emails' not in meta: + meta['old_emails'] = [] + meta['old_emails'].append([user.email, datetime.datetime.now().isoformat()]) + up.set_meta(meta) + up.save() + # Send it to the old email... + try: + user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + except Exception: + transaction.rollback() + log.warning('Unable to send confirmation email to old address', exc_info=True) + return render_to_response("email_change_failed.html", {'email': user.email}) - return render_to_response("email_change_successful.html", d) + user.email = pec.new_email + user.save() + pec.delete() + # And send it to the new email... + try: + user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + except Exception: + transaction.rollback() + log.warning('Unable to send confirmation email to new address', exc_info=True) + return render_to_response("email_change_failed.html", {'email': pec.new_email}) + + transaction.commit() + return render_to_response("email_change_successful.html", address_context) + except Exception: + # If we get an unexpected exception, be sure to rollback the transaction + transaction.rollback() + raise @ensure_csrf_cookie diff --git a/lms/templates/email_change_failed.html b/lms/templates/email_change_failed.html new file mode 100644 index 0000000000..e228df4a9c --- /dev/null +++ b/lms/templates/email_change_failed.html @@ -0,0 +1,3 @@ +

E-mail change failed.

+ +

We were unable to send a confirmation email to ${email}

diff --git a/pylintrc b/pylintrc index 792079ce03..d4085379b4 100644 --- a/pylintrc +++ b/pylintrc @@ -110,7 +110,9 @@ generated-members= get_url, size, content, - status_code + status_code, +# For factory_body factories + create [BASIC] diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 3d8b95f8e2..b5a72f8d95 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -71,7 +71,7 @@ transifex-client==0.8 coverage==3.6 factory_boy==2.0.2 lettuce==0.2.16 -mock==0.8.0 +mock==1.0.1 nosexcover==1.0.7 pep8==1.4.5 pylint==0.28 From 1224204b4c380f0831b05dd4961b9baba8158348 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 23 May 2013 12:03:02 -0400 Subject: [PATCH 44/57] Use the latest CodeJail, with 0 default for memory limit. --- requirements/edx/github.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 6b28d3edd9..f280d66557 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -9,4 +9,4 @@ # Our libraries: -e git+https://github.com/edx/XBlock.git@2144a25d#egg=XBlock --e git+https://github.com/edx/codejail.git@07494f1#egg=codejail +-e git+https://github.com/edx/codejail.git@72cf791#egg=codejail From 082e24c634b0331298d45f68917b0e414bf7c227 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Thu, 23 May 2013 12:09:12 -0400 Subject: [PATCH 45/57] Removed references to server_error.js --- cms/templates/checklists.html | 1 - cms/templates/course_info.html | 1 - cms/templates/settings.html | 1 - cms/templates/settings_advanced.html | 1 - .../settings_discussions_faculty.html | 29 +++++++++---------- cms/templates/settings_graders.html | 1 - 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/cms/templates/checklists.html b/cms/templates/checklists.html index e5227c71fd..6f78e952c0 100644 --- a/cms/templates/checklists.html +++ b/cms/templates/checklists.html @@ -9,7 +9,6 @@ - - diff --git a/cms/templates/settings.html b/cms/templates/settings.html index 5baf460e25..2adc0cd980 100644 --- a/cms/templates/settings.html +++ b/cms/templates/settings.html @@ -15,7 +15,6 @@ from contentstore import utils - diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html index 0400d95694..242148418e 100644 --- a/cms/templates/settings_advanced.html +++ b/cms/templates/settings_advanced.html @@ -11,7 +11,6 @@ from contentstore import utils <%block name="jsextra"> - diff --git a/cms/templates/settings_discussions_faculty.html b/cms/templates/settings_discussions_faculty.html index fc30b6eebb..5dfb4b76ae 100644 --- a/cms/templates/settings_discussions_faculty.html +++ b/cms/templates/settings_discussions_faculty.html @@ -6,27 +6,26 @@ <%namespace name='static' file='static_content.html'/> <%! -from contentstore import utils +from contentstore import utils %> <%block name="jsextra"> - - + <%block name="content">
-
+

Settings

@@ -74,7 +73,7 @@ from contentstore import utils
A brief description of your education, experience, and expertise -
+
Delete Faculty Member @@ -102,7 +101,7 @@ from contentstore import utils Upload Faculty Photo - Max size: 30KB + Max size: 30KB
@@ -114,7 +113,7 @@ from contentstore import utils A brief description of your education, experience, and expertise - + @@ -143,7 +142,7 @@ from contentstore import utils
- +
randomize all problems @@ -217,7 +216,7 @@ from contentstore import utils
- +
randomize all problems @@ -283,7 +282,7 @@ from contentstore import utils

Discussions

- +

General Settings

@@ -296,7 +295,7 @@ from contentstore import utils
- +
Students and faculty will be able to post anonymously @@ -320,7 +319,7 @@ from contentstore import utils
- +
Students and faculty will be able to post anonymously @@ -329,7 +328,7 @@ from contentstore import utils
- +
This option is disabled since there are previous discussions that are anonymous. @@ -351,7 +350,7 @@ from contentstore import utils - +
  • diff --git a/cms/templates/settings_graders.html b/cms/templates/settings_graders.html index ceab8cd862..2c6846bece 100644 --- a/cms/templates/settings_graders.html +++ b/cms/templates/settings_graders.html @@ -12,7 +12,6 @@ from contentstore import utils - From 3dd5733cee39c844b43f3b7e83bc7b3547d282a0 Mon Sep 17 00:00:00 2001 From: Giulio Gratta Date: Wed, 15 May 2013 18:21:39 -0500 Subject: [PATCH 46/57] first round of color replacements with variables --- lms/static/sass/base/_base.scss | 14 ++-- lms/static/sass/base/_extends.scss | 27 ++----- lms/static/sass/base/_variables.scss | 74 ++++++++++++++++++- lms/static/sass/course/_info.scss | 2 +- lms/static/sass/course/base/_base.scss | 15 ++-- lms/static/sass/course/base/_extends.scss | 2 +- .../sass/course/courseware/_sidebar.scss | 10 +-- .../course/layout/_courseware_header.scss | 6 +- lms/static/sass/course/layout/_footer.scss | 5 +- lms/static/sass/course/wiki/_wiki.scss | 4 +- .../sass/multicourse/_course_about.scss | 42 +++++------ lms/static/sass/multicourse/_courses.scss | 17 +++-- lms/static/sass/multicourse/_dashboard.scss | 42 ++++++----- lms/static/sass/multicourse/_home.scss | 43 +++++------ lms/static/sass/shared/_course_object.scss | 26 +++---- lms/static/sass/shared/_footer.scss | 2 +- lms/static/sass/shared/_forms.scss | 10 +-- lms/static/sass/shared/_header.scss | 47 ++++++++---- lms/static/sass/shared/_modal.scss | 4 +- 19 files changed, 238 insertions(+), 154 deletions(-) diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss index e62dd12541..6f43a02df7 100644 --- a/lms/static/sass/base/_base.scss +++ b/lms/static/sass/base/_base.scss @@ -2,8 +2,8 @@ // overflow-y: scroll; // } -body { - background: rgb(250,250,250); +html, body { + background: $body-bg; font-family: $sans-serif; font-size: 1em; font-style: normal; @@ -61,20 +61,20 @@ p + p, ul + p, ol + p { p { a:link, a:visited { - color: $blue; + color: $link-color; font: normal 1em/1em $serif; text-decoration: none; @include transition(all, 0.1s, linear); &:hover { - color: $blue; + color: $link-color; text-decoration: underline; } } } a:link, a:visited { - color: $blue; + color: $link-color; font: normal 1em/1em $sans-serif; text-decoration: none; @include transition(all, 0.1s, linear); @@ -87,8 +87,8 @@ a:link, a:visited { .content-wrapper { width: flex-grid(12); margin: 0 auto; + background: $content-wrapper-bg; padding-bottom: ($baseline*2); - background: rgb(255,255,255); } .container { @@ -164,7 +164,7 @@ mark { display: none; padding: 10px; @include linear-gradient(top, rgba(0, 0, 0, .1), rgba(0, 0, 0, .0)); - background-color: $pink; + background-color: $site-status-color; box-shadow: 0 -1px 0 rgba(0, 0, 0, .3) inset; font-size: 14px; diff --git a/lms/static/sass/base/_extends.scss b/lms/static/sass/base/_extends.scss index 2998e25dca..d244eff55f 100644 --- a/lms/static/sass/base/_extends.scss +++ b/lms/static/sass/base/_extends.scss @@ -1,39 +1,30 @@ .faded-hr-divider { - @include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%, - rgba(200,200,200, 1) 50%, - rgba(200,200,200, 0))); + @include background-image($faded-hr-image-1); height: 1px; width: 100%; } .faded-hr-divider-medium { - @include background-image(linear-gradient(180deg, rgba(240,240,240, 0) 0%, - rgba(240,240,240, 1) 50%, - rgba(240,240,240, 0))); + @include background-image($faded-hr-image-4); height: 1px; width: 100%; } .faded-hr-divider-light { - @include background-image(linear-gradient(180deg, rgba(255,255,255, 0) 0%, - rgba(255,255,255, 0.8) 50%, - rgba(255,255,255, 0))); + @include background-image($faded-hr-image-5); height: 1px; width: 100%; } .faded-vertical-divider { - @include background-image(linear-gradient(90deg, rgba(200,200,200, 0) 0%, - rgba(200,200,200, 1) 50%, - rgba(200,200,200, 0))); + @include background-image($faded-hr-image-1); height: 100%; width: 1px; } .faded-vertical-divider-light { - @include background-image(linear-gradient(90deg, rgba(255,255,255, 0) 0%, - rgba(255,255,255, 0.6) 50%, - rgba(255,255,255, 0))); + @include background-image($faded-hr-image-6); + background: transparent; height: 100%; width: 1px; } @@ -66,14 +57,12 @@ } .fade-right-hr-divider { - @include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%, - rgba(200,200,200, 1))); + @include background-image($faded-hr-image-2); border: none; } .fade-left-hr-divider { - @include background-image(linear-gradient(180deg, rgba(200,200,200, 1) 0%, - rgba(200,200,200, 0))); + @include background-image($faded-hr-image-3); border: none; } diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss index ddbd930323..2517045602 100644 --- a/lms/static/sass/base/_variables.scss +++ b/lms/static/sass/base/_variables.scss @@ -14,6 +14,14 @@ $monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace; $body-font-family: $sans-serif; $serif: $georgia; +$body-font-size: em(14); +$body-line-height: golden-ratio(.875em, 1); +$base-font-color: rgb(60,60,60); +$baseFontColor: rgb(60,60,60); +$base-font-color: rgb(60,60,60); +$lighter-base-font-color: rgb(100,100,100); +$very-light-text: #fff; + $white: rgb(255,255,255); $black: rgb(0,0,0); $blue: rgb(29,157,217); @@ -52,6 +60,66 @@ $baseFontColor: rgb(60,60,60); $lighter-base-font-color: rgb(100,100,100); $text-color: $dark-gray; -$body-font-family: $sans-serif; -$body-font-size: em(14); -$body-line-height: golden-ratio(.875em, 1); +$body-bg: rgb(250,250,250); +$header-image: linear-gradient(-90deg, rgba(255,255,255, 1), rgba(230,230,230, 0.9)); +$header-bg: transparent; +$courseware-header-image: linear-gradient(top, #fff, #eee); +$courseware-header-bg: transparent; +$footer-bg: transparent; +$courseware-footer-border: none; +$courseware-footer-shadow: none; +$courseware-footer-margin: 0px; + +$button-bg-image: linear-gradient(#fff 0%, rgb(250,250,250) 50%, rgb(237,237,237) 50%, rgb(220,220,220) 100%); +$button-bg-color: transparent; +$button-bg-hover-color: #fff; + +$faded-hr-image-1: linear-gradient(180deg, rgba(200,200,200, 0) 0%, rgba(200,200,200, 1) 50%, rgba(200,200,200, 0)); +$faded-hr-image-2: linear-gradient(180deg, rgba(200,200,200, 0) 0%, rgba(200,200,200, 1)); +$faded-hr-image-3: linear-gradient(180deg, rgba(200,200,200, 1) 0%, rgba(200,200,200, 0)); +$faded-hr-image-4: linear-gradient(180deg, rgba(240,240,240, 0) 0%, rgba(240,240,240, 1) 50%, rgba(240,240,240, 0)); +$faded-hr-image-5: linear-gradient(180deg, rgba(255,255,255, 0) 0%, rgba(255,255,255, 0.8) 50%, rgba(255,255,255, 0)); +$faded-hr-image-6: linear-gradient(90deg, rgba(255,255,255, 0) 0%, rgba(255,255,255, 0.6) 50%, rgba(255,255,255, 0)); + +$dashboard-profile-header-image: linear-gradient(-90deg, rgb(255,255,255), rgb(245,245,245)); +$dashboard-profile-header-color: transparent; +$dashboard-profile-color: rgb(252,252,252); +$dot-color: $light-gray; + +$content-wrapper-bg: rgb(255,255,255); +$course-bg-color: #d6d6d6; +$course-bg-image: url(../images/bg-texture.png); + +$course-profile-bg: rgb(245,245,245); +$course-header-bg: rgba(255,255,255, 0.93); + +$border-color-1: rgb(190,190,190); +$border-color-2: rgb(200,200,200); +$border-color-3: rgb(100,100,100); +$border-color-4: rgb(252,252,252); + +$link-color: $blue; +$link-hover: $pink; +$selection-color-1: $pink; +$selection-color-2: #444; +$site-status-color: $pink; + +$button-color: $blue; +$button-archive-color: #eee; + +$shadow-color: $blue; + +$sidebar-chapter-bg-top: rgba(255, 255, 255, .6); +$sidebar-chapter-bg-bottom: rgba(255, 255, 255, 0); +$sidebar-chapter-bg: #eee; +$sidebar-active-image: linear-gradient(top, #e6e6e6, #d6d6d6); + +$form-bg-color: #fff; +$modal-bg-color: rgb(245,245,245); + +//----------------- +// CSS BG Images +//----------------- +$homepage-bg-image: '/static/images/homepage-bg.jpg'; + +$video-thumb-url: '/static/images/courses/video-thumb.jpg'; \ No newline at end of file diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index bfd90505cf..741a7f9a22 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -117,7 +117,7 @@ div.info-wrapper { @include transition(all .2s); h4 { - color: $blue; + color: $link-color; font-size: 1em; font-weight: normal; padding-left: 30px; diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 6183c8a675..584412ca22 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -1,7 +1,8 @@ body { min-width: 980px; min-height: 100%; - background: url(../images/bg-texture.png) #d6d6d6; + background-image: $course-bg-image; + background-color: $course-bg-color; } body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label { @@ -34,7 +35,7 @@ a { width: 100%; border-radius: 3px; border: 1px solid $outer-border-color; - background: #fff; + background: $body-bg; @include box-shadow(0 1px 2px rgba(0, 0, 0, 0.05)); } } @@ -49,8 +50,8 @@ textarea, input[type="text"], input[type="email"], input[type="password"] { - background: rgb(250,250,250); - border: 1px solid rgb(200,200,200); + background: $body-bg; + border: 1px solid $border-color-2; @include border-radius(0); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6), inset 0 0 3px 0 rgba(0,0,0, 0.1)); @include box-sizing(border-box); @@ -65,7 +66,7 @@ input[type="password"] { } &:focus { - border-color: lighten($blue, 20%); + border-color: lighten($link-color, 20%); @include box-shadow(0 0 6px 0 rgba($blue, 0.4), inset 0 0 4px 0 rgba(0,0,0, 0.15)); outline: none; } @@ -94,7 +95,7 @@ img { } ::selection, ::-moz-selection, ::-webkit-selection { - background: #444; + background: $selection-color-2; color: #fff; } @@ -143,7 +144,7 @@ img { max-width: 350px; padding: 15px 20px 17px; border-radius: 3px; - border: 1px solid #333; + border: 1px solid $border-color-3; background: -webkit-linear-gradient(top, rgba(255, 255, 255, .1), rgba(255, 255, 255, 0)) rgba(30, 30, 30, .92); box-shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 1px 0 rgba(255, 255, 255, .1) inset; font-size: 13px; diff --git a/lms/static/sass/course/base/_extends.scss b/lms/static/sass/course/base/_extends.scss index bcb93a3645..a94a9511fe 100644 --- a/lms/static/sass/course/base/_extends.scss +++ b/lms/static/sass/course/base/_extends.scss @@ -1,5 +1,5 @@ h1.top-header { - border-bottom: 1px solid #e3e3e3; + border-bottom: 1px solid $border-color-2; text-align: left; font-size: em(24); font-weight: 100; diff --git a/lms/static/sass/course/courseware/_sidebar.scss b/lms/static/sass/course/courseware/_sidebar.scss index 81b497d4f9..6cf6f6a602 100644 --- a/lms/static/sass/course/courseware/_sidebar.scss +++ b/lms/static/sass/course/courseware/_sidebar.scss @@ -2,7 +2,7 @@ section.course-index { @extend .sidebar; @extend .tran; @include border-radius(3px 0 0 3px); - border-right: 1px solid #ddd; + border-right: 1px solid $border-color-2; #open_close_accordion { display: none; @@ -70,8 +70,8 @@ section.course-index { width: 100% !important; @include box-sizing(border-box); padding: 11px 14px; - @include linear-gradient(top, rgba(255, 255, 255, .6), rgba(255, 255, 255, 0)); - background-color: #eee; + @include linear-gradient(top, $sidebar-chapter-bg-top, $sidebar-chapter-bg-bottom); + background-color: $sidebar-chapter-bg; @include box-shadow(0 1px 0 #fff inset, 0 -1px 0 rgba(0, 0, 0, .1) inset); @include transition(background-color .1s); @@ -169,9 +169,9 @@ section.course-index { } > a { - border: 1px solid #bbb; + border: 1px solid $border-color-1; @include box-shadow(0 1px 0 rgba(255, 255, 255, .35) inset); - @include linear-gradient(top, #e6e6e6, #d6d6d6); + background: $sidebar-active-image; &:after { opacity: 1; diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index e27a6e99d8..4d8f000668 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -75,9 +75,9 @@ header.global.slim { &#login { display: block; - @include background-image(linear-gradient(-90deg, lighten($blue, 8%), lighten($blue, 5%) 50%, $blue 50%, darken($blue, 10%) 100%)); + @include background-image(linear-gradient(-90deg, lighten($link-color, 8%), lighten($link-color, 5%) 50%, $link-color 50%, darken($link-color, 10%) 100%)); border: 1px solid transparent; - border-color: darken($blue, 10%); + border-color: darken($link-color, 10%); @include border-radius(3px); @include box-sizing(border-box); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); @@ -97,7 +97,7 @@ header.global.slim { vertical-align: middle; &:hover, &.active { - @include background-image(linear-gradient(-90deg, $blue, $blue 50%, $blue 50%, $blue 100%)); + @include background-image(linear-gradient(-90deg, $link-color, $link-color 50%, $link-color 50%, $link-color 100%)); } } } diff --git a/lms/static/sass/course/layout/_footer.scss b/lms/static/sass/course/layout/_footer.scss index 7abf35a819..699846e781 100644 --- a/lms/static/sass/course/layout/_footer.scss +++ b/lms/static/sass/course/layout/_footer.scss @@ -1,4 +1,5 @@ footer { - border: none; - box-shadow: none; + border: $courseware-footer-border; + box-shadow: $courseware-footer-shadow; + margin-top: $courseware-footer-margin; } \ No newline at end of file diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index 1bc38abd9a..d064b6d345 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -113,7 +113,7 @@ section.wiki { } &:focus { - border-color: $blue; + border-color: $link-color; } } } @@ -276,7 +276,7 @@ section.wiki { li { &.active { a { - color: $blue; + color: $link-color; .icon-view, .icon-home { diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss index 195760721e..9eab7c0a4f 100644 --- a/lms/static/sass/multicourse/_course_about.scss +++ b/lms/static/sass/multicourse/_course_about.scss @@ -4,11 +4,11 @@ } header.course-profile { - background: rgb(245,245,245); - @include background-image(url('/static/images/homepage-bg.jpg')); + background: $course-profile-bg; + @include background-image(url($homepage-bg-image)); background-size: cover; @include box-shadow(0 1px 80px 0 rgba(0,0,0, 0.5)); - border-bottom: 1px solid rgb(100,100,100); + border-bottom: 1px solid $border-color-3; @include box-shadow(inset 0 1px 5px 0 rgba(0,0,0, 0.1)); height: 280px; margin-top: -69px; @@ -18,8 +18,8 @@ width: 100%; .intro-inner-wrapper { - background: rgba(255,255,255, 0.93); - border: 1px solid rgb(100,100,100); + background: $course-header-bg; + border: 1px solid $border-color-3; @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); @include box-sizing(border-box); @include clearfix; @@ -44,7 +44,7 @@ z-index: 2; > hgroup { - border-bottom: 1px solid rgb(210,210,210); + border-bottom: 1px solid $border-color-2; @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); margin-bottom: 20px; padding-bottom: 20px; @@ -68,7 +68,7 @@ text-transform: none; &:hover { - color: $blue; + color: $link-color; } } } @@ -85,7 +85,7 @@ text-transform: none; &:hover { - color: $blue; + color: $link-color; } } } @@ -99,7 +99,7 @@ width: flex-grid(12); > a.find-courses, a.register { - @include button(shiny, $blue); + @include button(shiny, $button-color); @include box-sizing(border-box); @include border-radius(3px); display: block; @@ -122,7 +122,7 @@ } strong { - @include button(shiny, $blue); + @include button(shiny, $button-color); @include box-sizing(border-box); @include border-radius(3px); display: block; @@ -140,10 +140,10 @@ } span.register { - background: lighten($blue, 20%); - border: 1px solid $blue; + background: $button-archive-color; + border: 1px solid darken($button-archive-color, 50%); @include box-sizing(border-box); - color: darken($blue, 20%); + color: darken($button-archive-color, 50%); display: block; letter-spacing: 1px; padding: 10px 0px 8px; @@ -176,7 +176,7 @@ z-index: 2; .hero { - border: 1px solid rgb(100,100,100); + border: 1px solid $border-color-3; height: 100%; overflow: hidden; position: relative; @@ -235,7 +235,7 @@ @include clearfix; nav { - border-bottom: 1px solid rgb(220,220,220); + border-bottom: 1px solid $border-color-2; @include box-sizing(border-box); @include clearfix; margin: 40px 0; @@ -262,7 +262,7 @@ } &:hover, &.active { - border-color: rgb(200,200,200); + border-color: $border-color-2; color: $base-font-color; text-decoration: none; } @@ -296,7 +296,7 @@ .teacher-image { background: rgb(255,255,255); - border: 1px solid rgb(200,200,200); + border: 1px solid $border-color-2; height: 115px; float: left; margin: 0 15px 0px 0; @@ -351,7 +351,7 @@ > section { @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15)); - border: 1px solid rgb(200,200,200); + border: 1px solid $border-color-2; &.course-summary { padding: 16px 20px 30px; @@ -401,7 +401,7 @@ } a.university-name { - border-right: 1px solid rgb(200,200,200); + border-right: 1px solid $border-color-2; color: $base-font-color; font-family: $sans-serif; font-style: italic; @@ -498,12 +498,12 @@ li { @include clearfix; - border-bottom: 1px dotted rgb(220,220,220); + border-bottom: 1px dotted $border-color-2; margin-bottom: 20px; padding-bottom: 10px; &.prerequisites { - border: 1px solid rgb(220,220,220); + border: 1px solid $border-color-2; margin: 0 -10px 0; padding: 10px; diff --git a/lms/static/sass/multicourse/_courses.scss b/lms/static/sass/multicourse/_courses.scss index 45ecfcd23f..2bea31bb99 100644 --- a/lms/static/sass/multicourse/_courses.scss +++ b/lms/static/sass/multicourse/_courses.scss @@ -1,12 +1,13 @@ .find-courses, .university-profile { - background: rgb(252,252,252); + background: $course-profile-bg; padding-bottom: 60px; header.search { - background: rgb(240,240,240); + background: $course-profile-bg; background-size: cover; + @include background-image(url($homepage-bg-image)); background-position: center top !important; - border-bottom: 1px solid rgb(100,100,100); + border-bottom: 1px solid $border-color-3; @include box-shadow(inset 0 -1px 8px 0 rgba(0,0,0, 0.2), inset 0 1px 12px 0 rgba(0,0,0, 0.3)); height: 430px; margin-top: -69px; @@ -24,8 +25,8 @@ > hgroup { background: #FFF; - background: rgba(255,255,255, 0.93); - border: 1px solid rgb(100,100,100); + background: $course-header-bg; + border: 1px solid $border-color-3; @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); padding: 20px 30px; position: relative; @@ -41,7 +42,7 @@ .logo { @include inline-block; - height: 80px; + height: 100px; margin-right: 30px; padding-right: 30px; position: relative; @@ -51,7 +52,7 @@ @extend .faded-vertical-divider; content: ""; display: block; - height: 80px; + height: 100px; position: absolute; right: 0px; top: 0px; @@ -83,7 +84,7 @@ } section.message { - border-top: 1px solid rgb(220,220,220); + border-top: 1px solid $border-color-2; @include clearfix; margin-top: 20px; padding-top: 60px; diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index cc54b9b242..b173647550 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -30,8 +30,9 @@ width: flex-grid(3); header.profile { - @include background-image(linear-gradient(-90deg, rgb(255,255,255), rgb(245,245,245))); - border: 1px solid rgb(200,200,200); + @include background-image($dashboard-profile-header-image); + background-color: $dashboard-profile-header-color; + border: 1px solid $border-color-2; @include border-radius(4px); @include box-sizing(border-box); width: flex-grid(12); @@ -53,8 +54,8 @@ padding: 0px 10px; > ul { - background: rgb(252,252,252); - border: 1px solid rgb(200,200,200); + background: $dashboard-profile-color; + border: 1px solid $border-color-2; border-top: none; //@include border-bottom-radius(4px); @include box-sizing(border-box); @@ -66,7 +67,7 @@ li { @include clearfix; - border-bottom: 1px dotted rgb(220,220,220); + border-bottom: 1px dotted $border-color-2; list-style: none; margin-bottom: 15px; padding-bottom: 17px; @@ -128,8 +129,8 @@ .news-carousel { @include clearfix; margin: 30px 10px 0; - border: 1px solid rgb(200,200,200); - background: rgb(252,252,252); + border: 1px solid $border-color-2; + background: $dashboard-profile-color; @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15)); * { @@ -156,14 +157,14 @@ width: 11px; height: 11px; border-radius: 11px; - background: $light-gray; + background: $dot-color; &:hover { - background: #ccc; + background: $lighter-base-font-color; } &.current { - background: $blue; + background: $link-color; } } @@ -201,7 +202,7 @@ img { width: 100%; - border: 1px solid $light-gray; + border: 1px solid $border-color-1; } } @@ -229,7 +230,7 @@ width: flex-grid(9); > header { - border-bottom: 1px solid rgb(210,210,210); + border-bottom: 1px solid $border-color-2; margin-bottom: 30px; } @@ -246,8 +247,9 @@ a { background: rgb(240,240,240); - @include background-image(linear-gradient(-90deg, rgb(245,245,245) 0%, rgb(243,243,243) 50%, rgb(237,237,237) 50%, rgb(235,235,235) 100%)); - border: 1px solid rgb(220,220,220); + @include background-image($button-bg-image); + background-color: $button-bg-color; + border: 1px solid $border-color-2; @include border-radius(4px); @include box-shadow(0 1px 8px 0 rgba(0,0,0, 0.1)); @include box-sizing(border-box); @@ -260,7 +262,7 @@ text-shadow: 0 1px rgba(255,255,255, 0.6); &:hover { - color: $blue; + color: $link-color; text-decoration: none; } } @@ -272,7 +274,7 @@ margin-right: flex-gutter(); margin-bottom: 50px; padding-bottom: 50px; - border-bottom: 1px solid $light-gray; + border-bottom: 1px solid $border-color-1; position: relative; width: flex-grid(12); z-index: 20; @@ -343,7 +345,7 @@ .course-status { background: $yellow; - border: 1px solid rgb(200,200,200); + border: 1px solid $border-color-2; @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); margin-top: 17px; margin-right: flex-gutter(); @@ -362,7 +364,7 @@ .course-status-completed { background: #ccc; - color: #fff; + color: $very-light-text; p { color: #222; @@ -374,7 +376,7 @@ } .enter-course { - @include button(simple, $blue); + @include button(simple, $button-color); @include box-sizing(border-box); @include border-radius(3px); display: block; @@ -386,7 +388,7 @@ margin-top: 16px; &.archived { - @include button(simple, #eee); + @include button(simple, $button-archive-color); font: normal 15px/1.6rem $sans-serif; padding: 6px 32px 7px; diff --git a/lms/static/sass/multicourse/_home.scss b/lms/static/sass/multicourse/_home.scss index b5546aa470..816adf575b 100644 --- a/lms/static/sass/multicourse/_home.scss +++ b/lms/static/sass/multicourse/_home.scss @@ -7,15 +7,16 @@ } > header { - background: rgb(255,255,255); - @include background-image(url('/static/images/homepage-bg.jpg')); + background: $dashboard-profile-color; + @include background-image(url($homepage-bg-image)); background-size: cover; - border-bottom: 1px solid rgb(80,80,80); - @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.9), inset 0 -1px 5px 0 rgba(0,0,0, 0.1)); + border-bottom: 1px solid $border-color-3; + @include box-shadow(0 1px 0 0 $course-header-bg, inset 0 -1px 5px 0 rgba(0,0,0, 0.1)); @include clearfix; height: 460px; - margin-top: -69px; + float: left; overflow: hidden; + margin-top: -69px; padding: 0px; width: flex-grid(12); @@ -31,8 +32,8 @@ .title { background: #FFF; - background: rgba(255,255,255, 0.93); - border: 1px solid rgb(100,100,100); + background: $course-header-bg; + border: 1px solid $border-color-3; @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); @include box-sizing(border-box); min-height: 120px; @@ -80,8 +81,8 @@ .media { background: #FFF; - background: rgba(255,255,255, 0.93); - border: 1px solid rgb(100,100,100); + background: $course-header-bg; + border: 1px solid $border-color-3; border-left: 0; @include box-sizing(border-box); // @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); @@ -101,7 +102,7 @@ height: 100%; overflow: hidden; position: relative; - background: url('../images/courses/video-thumb.jpg') center no-repeat; + background: url($video-thumb-url) center no-repeat; @include background-size(cover); .play-intro { @@ -164,15 +165,15 @@ > h2 { @include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(230,230,230))); - border: 1px solid rgb(200,200,200); + border: 1px solid $border-color-2; @include border-radius(4px); - border-top-color: rgb(190,190,190); + border-top-color: $border-color-1; @include box-shadow(inset 0 0 0 1px rgba(255,255,255, 0.4), 0 0px 12px 0 rgba(0,0,0, 0.2)); color: $lighter-base-font-color; letter-spacing: 1px; margin-bottom: 0px; margin-top: -15px; - padding: 10px 10px 8px; + padding: 10px 10px 12px; text-align: center; text-transform: uppercase; text-shadow: 0 1px rgba(255,255,255, 0.6); @@ -180,7 +181,7 @@ } .university-partners { - border-bottom: 1px solid rgb(210,210,210); + border-bottom: 1px solid $border-color-2; margin-bottom: 0px; overflow: hidden; position: relative; @@ -366,13 +367,13 @@ } .more-info { - border: 1px solid rgb(200,200,200); + border: 1px solid $border-color-2; margin-bottom: 80px; width: flex-grid(12); header { @include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(230,230,230))); - border-bottom: 1px solid rgb(200,200,200); + border-bottom: 1px solid $border-color-2; @include clearfix; padding: 10px 20px 8px; position: relative; @@ -415,14 +416,14 @@ width: flex-grid(12); .blog-posts { - border-bottom: 1px solid rgb(220,220,220); + border-bottom: 1px solid $border-color-2; margin-bottom: 20px; padding-bottom: 20px; @include clearfix; > article { border: 1px dotted transparent; - border-color: rgb(220,220,220); + border-color: $border-color-2; @include box-sizing(border-box); @include clearfix; float: left; @@ -432,8 +433,8 @@ width: flex-grid(4); &:hover { - background: rgb(248,248,248); - border: 1px solid rgb(220,220,220); + background: $body-bg; + border: 1px solid $border-color-2; @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.1)); } @@ -442,7 +443,7 @@ } .post-graphics { - border: 1px solid rgb(190,190,190); + border: 1px solid $border-color-1; @include box-sizing(border-box); display: block; float: left; diff --git a/lms/static/sass/shared/_course_object.scss b/lms/static/sass/shared/_course_object.scss index e99559a49f..f78c483925 100644 --- a/lms/static/sass/shared/_course_object.scss +++ b/lms/static/sass/shared/_course_object.scss @@ -31,8 +31,8 @@ } .course { - background: rgb(250,250,250); - border: 1px solid rgb(180,180,180); + background: $body-bg; + border: 1px solid $border-color-1; @include border-radius(2px); @include box-sizing(border-box); @include box-shadow(0 1px 10px 0 rgba(0,0,0, 0.15), inset 0 0 0 1px rgba(255,255,255, 0.9)); @@ -42,7 +42,7 @@ @include transition(all, 0.15s, linear); .status { - background: $blue; + background: $link-color; color: white; font-size: 10px; left: 10px; @@ -55,7 +55,7 @@ } .status:after { - border-bottom: 6px solid shade($blue, 50%); + border-bottom: 6px solid shade($link-color, 50%); border-right: 6px solid transparent; content: ""; display: block; @@ -90,7 +90,7 @@ } .inner-wrapper { - border: 1px solid rgba(255,255,255, 1); + border: 1px solid $border-color-4; height: 100%; height: 200px; overflow: hidden; @@ -116,12 +116,12 @@ text-decoration: none; .info-link { - color: $blue; + color: $link-color; opacity: 1; } h2 { - color: $blue; + color: $link-color; } } @@ -176,7 +176,7 @@ // } .info { - background: rgb(255,255,255); + background: $content-wrapper-bg; height: 220px + 130px; left: 0px; position: absolute; @@ -221,14 +221,14 @@ width: 100%; .university { - border-right: 1px solid rgb(200,200,200); + border-right: 1px solid $border-color-2; color: $lighter-base-font-color; letter-spacing: 1px; margin-right: 10px; padding-right: 10px; &:hover { - color: $blue; + color: $link-color; } } @@ -240,9 +240,9 @@ } &:hover { - background: rgb(245,245,245); - border-color: rgb(170,170,170); - @include box-shadow(0 1px 16px 0 rgba($blue, 0.4)); + background: $course-profile-bg; + border-color: $border-color-1; + @include box-shadow(0 1px 16px 0 rgba($shadow-color, 0.4)); .info { top: -150px; diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index d891ff408b..e3e99ae301 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -159,4 +159,4 @@ width: 360px; } } -} \ No newline at end of file +} diff --git a/lms/static/sass/shared/_forms.scss b/lms/static/sass/shared/_forms.scss index 79d476f420..3350081850 100644 --- a/lms/static/sass/shared/_forms.scss +++ b/lms/static/sass/shared/_forms.scss @@ -15,8 +15,8 @@ input[type="text"], input[type="email"], input[type="password"], input[type="tel"] { - background: rgb(250,250,250); - border: 1px solid rgb(200,200,200); + background: $form-bg-color; + border: 1px solid $border-color-2; @include border-radius(3px); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6), inset 0 0 3px 0 rgba(0,0,0, 0.1)); @include box-sizing(border-box); @@ -31,8 +31,8 @@ input[type="tel"] { } &:focus { - border-color: lighten($blue, 20%); - @include box-shadow(0 0 6px 0 rgba($blue, 0.4), inset 0 0 4px 0 rgba(0,0,0, 0.15)); + border-color: darken($button-archive-color, 50%); + @include box-shadow(0 0 6px 0 darken($button-archive-color, 50%), inset 0 0 4px 0 rgba(0,0,0, 0.15)); outline: none; } } @@ -46,7 +46,7 @@ input[type="button"], button, .button { @include border-radius(3px); - @include button(shiny, $blue); + @include button(shiny, $button-color); font: normal 1.2rem/1.6rem $sans-serif; letter-spacing: 1px; padding: 4px 20px; diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index 5eb453448c..878fdae70a 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -22,6 +22,28 @@ header.global { margin: -2px 39px 0px 0px; position: relative; + &::before { + @extend .faded-vertical-divider; + content: ""; + display: block; + height: 50px; + position: absolute; + right: 1px; + top: -5px; + width: 1px; + } + + &::after { + @extend .faded-vertical-divider-light; + content: ""; + display: block; + height: 50px; + position: absolute; + right: 0px; + top: -10px; + width: 1px; + } + a { display: block; } @@ -55,7 +77,6 @@ header.global { li.secondary { > a { color: $lighter-base-font-color; - color: $blue; display: block; font-family: $sans-serif; @include inline-block; @@ -78,9 +99,9 @@ header.global { margin-right: 5px; > a { - @include background-image(linear-gradient(#fff 0%, rgb(250,250,250) 50%, rgb(237,237,237) 50%, rgb(220,220,220) 100%)); - border: 1px solid transparent; - border-color: rgb(200,200,200); + @include background-image($button-bg-image); + background-color: $button-bg-color; + border: 1px solid $border-color-2; @include border-radius(3px); @include box-sizing(border-box); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); @@ -101,7 +122,7 @@ header.global { } &:hover, &.active { - background: #FFF; + background: $button-bg-hover-color; } } } @@ -159,10 +180,10 @@ header.global { } ul.dropdown-menu { - background: rgb(252,252,252); + background: $border-color-4; @include border-radius(4px); @include box-shadow(0 2px 24px 0 rgba(0,0,0, 0.3)); - border: 1px solid rgb(100,100,100); + border: 1px solid $border-color-3; display: none; padding: 5px 10px; position: absolute; @@ -178,12 +199,12 @@ header.global { &::before { background: transparent; border: { - top: 6px solid rgba(252,252,252, 1); - right: 6px solid rgba(252,252,252, 1); + top: 6px solid $border-color-4; + right: 6px solid $border-color-4; bottom: 6px solid transparent; left: 6px solid transparent; } - @include box-shadow(1px 0 0 0 rgb(0,0,0), 0 -1px 0 0 rgb(0,0,0)); + @include box-shadow(1px 0 0 0 $border-color-3, 0 -1px 0 0 $border-color-3); content: ""; display: block; height: 0px; @@ -196,7 +217,7 @@ header.global { li { display: block; - border-top: 1px dotted rgba(200,200,200, 1); + border-top: 1px dotted $border-color-2; @include box-shadow(inset 0 1px 0 0 rgba(255,255,255, 0.05)); &:first-child { @@ -208,7 +229,7 @@ header.global { border: 1px solid transparent; @include border-radius(3px); @include box-sizing(border-box); - color: $blue; + color: $link-color; cursor: pointer; display: block; margin: 5px 0px; @@ -328,4 +349,4 @@ header.global { text-decoration: none; color: $m-blue-s1 !important; } -} \ No newline at end of file +} diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index 8ff58c1c14..7a51213dee 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -52,7 +52,7 @@ } .inner-wrapper { - background: rgb(245,245,245); + background: $modal-bg-color; @include border-radius(0px); border: 1px solid rgba(0, 0, 0, 0.9); @include box-shadow(inset 0 1px 0 0 rgba(255, 255, 255, 0.7)); @@ -149,7 +149,7 @@ } label { - color: #646464; + color: $text-color; &.field-error { display: block; From 93258c548f6d2d3732562d1d4a0c122887259689 Mon Sep 17 00:00:00 2001 From: Giulio Gratta Date: Thu, 16 May 2013 18:14:22 -0500 Subject: [PATCH 47/57] fixed hardcoded changes left over from parent theme branch backport --- lms/static/sass/multicourse/_courses.scss | 4 ++-- lms/static/sass/multicourse/_home.scss | 3 +-- lms/static/sass/shared/_header.scss | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lms/static/sass/multicourse/_courses.scss b/lms/static/sass/multicourse/_courses.scss index 2bea31bb99..ac31da4d2a 100644 --- a/lms/static/sass/multicourse/_courses.scss +++ b/lms/static/sass/multicourse/_courses.scss @@ -42,7 +42,7 @@ .logo { @include inline-block; - height: 100px; + height: 80px; margin-right: 30px; padding-right: 30px; position: relative; @@ -52,7 +52,7 @@ @extend .faded-vertical-divider; content: ""; display: block; - height: 100px; + height: 80px; position: absolute; right: 0px; top: 0px; diff --git a/lms/static/sass/multicourse/_home.scss b/lms/static/sass/multicourse/_home.scss index 816adf575b..ea8ddaf654 100644 --- a/lms/static/sass/multicourse/_home.scss +++ b/lms/static/sass/multicourse/_home.scss @@ -14,7 +14,6 @@ @include box-shadow(0 1px 0 0 $course-header-bg, inset 0 -1px 5px 0 rgba(0,0,0, 0.1)); @include clearfix; height: 460px; - float: left; overflow: hidden; margin-top: -69px; padding: 0px; @@ -173,7 +172,7 @@ letter-spacing: 1px; margin-bottom: 0px; margin-top: -15px; - padding: 10px 10px 12px; + padding: 10px 10px 8px; text-align: center; text-transform: uppercase; text-shadow: 0 1px rgba(255,255,255, 0.6); diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index 878fdae70a..0ec3229ade 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -29,7 +29,7 @@ header.global { height: 50px; position: absolute; right: 1px; - top: -5px; + top: -8px; width: 1px; } @@ -40,7 +40,7 @@ header.global { height: 50px; position: absolute; right: 0px; - top: -10px; + top: -12px; width: 1px; } @@ -77,6 +77,7 @@ header.global { li.secondary { > a { color: $lighter-base-font-color; + color: $link-color display: block; font-family: $sans-serif; @include inline-block; From 566a8b3823b0e9776a335e854c0807fdc202129d Mon Sep 17 00:00:00 2001 From: Giulio Gratta Date: Wed, 22 May 2013 10:30:25 -0700 Subject: [PATCH 48/57] fix bad css and clean up after rebase/merge --- lms/static/sass/shared/_header.scss | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index 0ec3229ade..6987b35c84 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -22,28 +22,6 @@ header.global { margin: -2px 39px 0px 0px; position: relative; - &::before { - @extend .faded-vertical-divider; - content: ""; - display: block; - height: 50px; - position: absolute; - right: 1px; - top: -8px; - width: 1px; - } - - &::after { - @extend .faded-vertical-divider-light; - content: ""; - display: block; - height: 50px; - position: absolute; - right: 0px; - top: -12px; - width: 1px; - } - a { display: block; } @@ -76,8 +54,7 @@ header.global { li.secondary { > a { - color: $lighter-base-font-color; - color: $link-color + color: $link-color; display: block; font-family: $sans-serif; @include inline-block; From cf1e906c130e4b7c6a7f4520440052a1fc041dfd Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Thu, 23 May 2013 12:50:19 -0400 Subject: [PATCH 49/57] Fix lettuce tests --- .../contentstore/features/advanced-settings.py | 4 +--- cms/djangoapps/contentstore/features/checklists.py | 4 +--- .../contentstore/features/course-settings.py | 4 +--- cms/djangoapps/contentstore/features/courses.py | 2 +- cms/djangoapps/contentstore/features/section.py | 2 +- common/djangoapps/terrain/ui_helpers.py | 14 ++++++++++++++ 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index ea5b24b21f..3acebecac8 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -19,9 +19,7 @@ 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' - if world.browser.is_element_present_by_css(expand_icon_css): - world.css_click(expand_icon_css) + world.click_course_settings() link_css = 'li.nav-course-settings-advanced a' world.css_click(link_css) diff --git a/cms/djangoapps/contentstore/features/checklists.py b/cms/djangoapps/contentstore/features/checklists.py index 1c9fbf0994..9552d35036 100644 --- a/cms/djangoapps/contentstore/features/checklists.py +++ b/cms/djangoapps/contentstore/features/checklists.py @@ -10,9 +10,7 @@ from selenium.common.exceptions import StaleElementReferenceException ############### ACTIONS #################### @step('I select Checklists from the Tools menu$') def i_select_checklists(step): - expand_icon_css = 'li.nav-course-tools i.icon-expand' - if world.browser.is_element_present_by_css(expand_icon_css): - world.css_click(expand_icon_css) + world.click_tools() link_css = 'li.nav-course-tools-checklists a' world.css_click(link_css) diff --git a/cms/djangoapps/contentstore/features/course-settings.py b/cms/djangoapps/contentstore/features/course-settings.py index d69266b7de..bd86fff9b7 100644 --- a/cms/djangoapps/contentstore/features/course-settings.py +++ b/cms/djangoapps/contentstore/features/course-settings.py @@ -25,9 +25,7 @@ DEFAULT_TIME = "00:00" ############### ACTIONS #################### @step('I select Schedule and Details$') def test_i_select_schedule_and_details(step): - expand_icon_css = 'li.nav-course-settings i.icon-expand' - if world.browser.is_element_present_by_css(expand_icon_css): - world.css_click(expand_icon_css) + world.click_course_settings() link_css = 'li.nav-course-settings-schedule a' world.css_click(link_css) diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index 5da7720945..aa2e9d68f8 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -62,4 +62,4 @@ def i_am_on_tab(step, 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 world.css_has_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 59c5a37b33..9a896d8ebe 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -62,7 +62,7 @@ def i_click_to_edit_section_name(step): @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' + css = '.section-name-edit input[type=text]' assert world.is_css_present(css) assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"') diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 40b839ae24..79e9b0afdb 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -123,3 +123,17 @@ def save_the_html(path='/tmp'): f = open('%s/%s' % (path, filename), 'w') f.write(html) f.close() + + +@world.absorb +def click_course_settings(): + course_settings_css = 'li.nav-course-settings' + if world.browser.is_element_present_by_css(course_settings_css): + world.css_click(course_settings_css) + + +@world.absorb +def click_tools(): + tools_css = 'li.nav-course-tools' + if world.browser.is_element_present_by_css(tools_css): + world.css_click(tools_css) From 538ffe6fbe83fad34f10bcc6cad9454089e4c5ea Mon Sep 17 00:00:00 2001 From: Giulio Gratta Date: Thu, 23 May 2013 11:04:24 -0700 Subject: [PATCH 50/57] fixing video-bg-image and homepage-bg-image urls --- lms/static/sass/base/_variables.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss index 2517045602..6bd593c28c 100644 --- a/lms/static/sass/base/_variables.scss +++ b/lms/static/sass/base/_variables.scss @@ -120,6 +120,6 @@ $modal-bg-color: rgb(245,245,245); //----------------- // CSS BG Images //----------------- -$homepage-bg-image: '/static/images/homepage-bg.jpg'; +$homepage-bg-image: '../images/homepage-bg.jpg'; -$video-thumb-url: '/static/images/courses/video-thumb.jpg'; \ No newline at end of file +$video-thumb-url: '../images/courses/video-thumb.jpg'; \ No newline at end of file From b6090952daf9167982a95f9d1976d6be81619ba9 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Thu, 23 May 2013 14:24:36 -0400 Subject: [PATCH 51/57] randomize_module.py was missing 'import lxml' to support definition_to_xml(). Also, fix serialization of show_timezone to expect a bool --- common/lib/xmodule/xmodule/randomize_module.py | 2 ++ common/lib/xmodule/xmodule/xml_module.py | 1 + common/test/data/full/course.xml | 2 +- .../sequential/Administrivia_and_Circuit_Elements.xml | 9 +++++++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/randomize_module.py b/common/lib/xmodule/xmodule/randomize_module.py index 240f33e33e..434706530b 100644 --- a/common/lib/xmodule/xmodule/randomize_module.py +++ b/common/lib/xmodule/xmodule/randomize_module.py @@ -4,6 +4,8 @@ import random from xmodule.x_module import XModule from xmodule.seq_module import SequenceDescriptor +from lxml import etree + from xblock.core import Scope, Integer log = logging.getLogger('mitx.' + __name__) diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index 7480cda0c5..2f54bbf405 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -136,6 +136,7 @@ class XmlDescriptor(XModuleDescriptor): 'hide_progress_tab': bool_map, 'allow_anonymous': bool_map, 'allow_anonymous_to_peers': bool_map, + 'show_timezone': bool_map, } diff --git a/common/test/data/full/course.xml b/common/test/data/full/course.xml index 7a05db42f2..b2f9097020 100644 --- a/common/test/data/full/course.xml +++ b/common/test/data/full/course.xml @@ -1 +1 @@ - + diff --git a/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml b/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml index 26f8f5a08d..47b19f75ed 100644 --- a/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml +++ b/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml @@ -12,4 +12,13 @@ Minor correction: Six elements (five resistors)… + + + + + +

    Inline content…

    + +
    +
    From bfaadd47ab1f77e8e20e0c8ca3d6c25a6898b8e9 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Thu, 23 May 2013 22:30:09 -0400 Subject: [PATCH 52/57] Updated testing documentation to reflect changes to how requirements are installed. --- doc/testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/testing.md b/doc/testing.md index c334317de7..bf70cdcce7 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -88,7 +88,7 @@ because the `capa` package handles problem XML. Before running tests, ensure that you have all the dependencies. You can install dependencies using: - pip install -r requirements.txt + rake install_prereqs ## Running Python Unit tests @@ -187,7 +187,7 @@ To run tests faster by not collecting static files, you can use **Troubleshooting**: If you get an error message that says something about harvest not being a command, you probably are missing a requirement. Try running: - pip install -r requirements.txt + rake install_prereqs **Note**: The acceptance tests can *not* currently run in parallel. From dfb623a29f6c967335873ad9134241c5a7dedbb5 Mon Sep 17 00:00:00 2001 From: Frances Botsford Date: Fri, 24 May 2013 09:01:15 -0400 Subject: [PATCH 53/57] removed old icon css and js files and references that are no longer needed --- cms/templates/base.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cms/templates/base.html b/cms/templates/base.html index 5fa347b6bf..e0e3e94d26 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -20,8 +20,6 @@ <%static:css group='base-style'/> - - <%include file="widgets/segment-io.html" /> @@ -43,8 +41,6 @@ - - <%static:js group='main'/> <%static:js group='module-js'/> From a64476f39a6e37c52776aac9873156856695c8c3 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 24 May 2013 11:08:52 -0400 Subject: [PATCH 54/57] Removed obsolete troubleshooting tip. --- doc/testing.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/testing.md b/doc/testing.md index bf70cdcce7..e5d035d90e 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -183,12 +183,6 @@ To start the debugger on failure, add the `--pdb` option: To run tests faster by not collecting static files, you can use `rake fasttest_acceptance_lms` and `rake fasttest_acceptance_cms`. - -**Troubleshooting**: If you get an error message that says something about harvest not being a command, you probably are missing a requirement. -Try running: - - rake install_prereqs - **Note**: The acceptance tests can *not* currently run in parallel. ## Viewing Test Coverage From 9cbb4e090d37779695f846063441796cdb155e28 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 24 May 2013 14:40:33 -0400 Subject: [PATCH 55/57] Swap icons for font awesome --- cms/templates/widgets/open-ended-edit.html | 6 +++--- .../xmodule/css/combinedopenended/edit.scss | 20 ++----------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/cms/templates/widgets/open-ended-edit.html b/cms/templates/widgets/open-ended-edit.html index 95c28b8393..d990cae189 100644 --- a/cms/templates/widgets/open-ended-edit.html +++ b/cms/templates/widgets/open-ended-edit.html @@ -5,11 +5,11 @@
    • + class="combinedopenended-editor-icon icon-quote-left">
    • + class="combinedopenended-editor-icon icon-columns">
    • + class="combinedopenended-editor-icon icon-sitemap">
    • Advanced Editor
    • diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss b/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss index 662294d488..23fa2e0b80 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss @@ -101,22 +101,6 @@ .combinedopenended-editor-icon { display: inline-block; - width: 26px; - height: 21px; vertical-align: middle; - background: url(../img/problem-editor-icons.png) no-repeat; -} - -.combinedopenended-editor-icon.multiple-choice { - background-position: 0 0; -} - -.combinedopenended-editor-icon.explanation { - width: 28px; - background-position: -111px 0; -} - -.combinedopenended-editor-icon.dropdown { - width: 28px; - background-position: -111px 0; -} + color: #333; +} \ No newline at end of file From cef401f2462abf5e3acbe1789359de882fbae457 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 24 May 2013 16:20:59 -0400 Subject: [PATCH 56/57] Swap icon to icon-table --- cms/templates/widgets/open-ended-edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/templates/widgets/open-ended-edit.html b/cms/templates/widgets/open-ended-edit.html index d990cae189..5d0dfcd48b 100644 --- a/cms/templates/widgets/open-ended-edit.html +++ b/cms/templates/widgets/open-ended-edit.html @@ -7,7 +7,7 @@
    • + class="combinedopenended-editor-icon icon-table">
    From e17276ade86dc055745befcfb967a9db34df3a92 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 24 May 2013 16:43:29 -0400 Subject: [PATCH 57/57] Fix some pep8 stuff --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 8 ++++---- .../lib/xmodule/xmodule/css/combinedopenended/edit.scss | 2 +- .../xmodule/js/src/combinedopenended/display.coffee | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 41e0d13c72..0e32770afc 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -54,15 +54,15 @@ class CombinedOpenEndedFields(object): state = String(help="Which step within the current task that the student is on.", default="initial", scope=Scope.user_state) student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, - scope=Scope.user_state) + scope=Scope.user_state) ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False, - scope=Scope.user_state) + scope=Scope.user_state) attempts = StringyInteger(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings) is_graded = StringyBoolean(help="Whether or not the problem is graded.", default=False, scope=Scope.settings) accept_file_upload = StringyBoolean(help="Whether or not the problem accepts file uploads.", default=False, - scope=Scope.settings) + scope=Scope.settings) skip_spelling_checks = StringyBoolean(help="Whether or not to skip initial spelling checks.", default=True, - scope=Scope.settings) + scope=Scope.settings) due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings) graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None, scope=Scope.settings) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss b/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss index 23fa2e0b80..94515ff4b7 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/edit.scss @@ -103,4 +103,4 @@ display: inline-block; vertical-align: middle; color: #333; -} \ No newline at end of file +} diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index bbd05e7f59..d4c2ff00ae 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -576,4 +576,4 @@ class @CombinedOpenEnded $('.file-upload-preview')[0].width = width_px/scale_factor $('.file-upload-preview')[0].height = height_px/scale_factor $('.file-upload-preview').show() - reader.readAsDataURL($('.file-upload-box')[0].files[0]) \ No newline at end of file + reader.readAsDataURL($('.file-upload-box')[0].files[0])