From 57eca325d2f170a3eb1f07aaa9f6cc4ef7693afb Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 6 May 2013 15:59:35 -0400 Subject: [PATCH 01/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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 a44e8168773749b9c70c587eb6c0ac9092cb4daf Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Mon, 20 May 2013 10:33:52 -0400 Subject: [PATCH 16/37] remove_open_ended_panel_tab() -> remove_extra_paneltab(). Also, update new forums test factors to use refactored Models.py --- cms/djangoapps/contentstore/views/course.py | 2 +- lms/djangoapps/django_comment_client/tests/factories.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 9cfe9c207a..07f6b9669c 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -18,7 +18,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationErr 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_extra_panel_tab, remove_open_ended_panel_tab +from contentstore.utils import get_lms_link_for_item, add_extra_panel_tab, remove_extra_panel_tab from models.settings.course_details import CourseDetails, CourseSettingsEncoder from models.settings.course_grading import CourseGradingModel from models.settings.course_metadata import CourseMetadata diff --git a/lms/djangoapps/django_comment_client/tests/factories.py b/lms/djangoapps/django_comment_client/tests/factories.py index eb1d9477c3..4a82c8f1bb 100644 --- a/lms/djangoapps/django_comment_client/tests/factories.py +++ b/lms/djangoapps/django_comment_client/tests/factories.py @@ -1,5 +1,5 @@ from factory import DjangoModelFactory -from django_comment_client.models import Role, Permission +from django_comment_common.models import Role, Permission class RoleFactory(DjangoModelFactory): From ec09ac7f29caa2a2385693e5d9603bd41263ee78 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 20 May 2013 12:57:07 -0400 Subject: [PATCH 17/37] Put numpy into a pre.txt so that everything will actually install. --- rakefiles/prereqs.rake | 1 + requirements/edx/base.txt | 1 - requirements/edx/pre.txt | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 requirements/edx/pre.txt diff --git a/rakefiles/prereqs.rake b/rakefiles/prereqs.rake index ef4958e9d7..e92aeb3502 100644 --- a/rakefiles/prereqs.rake +++ b/rakefiles/prereqs.rake @@ -31,6 +31,7 @@ task :install_python_prereqs => "ws:migrate" do unchanged = 'Python requirements unchanged, nothing to install' when_changed(unchanged, 'requirements/**/*') do ENV['PIP_DOWNLOAD_CACHE'] ||= '.pip_download_cache' + sh('pip install --exists-action w -r requirements/edx/pre.txt') sh('pip install --exists-action w -r requirements/edx/base.txt') sh('pip install --exists-action w -r requirements/edx/post.txt') # Check for private-requirements.txt: used to install our libs as working dirs, diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index ef0209ee03..27d6ced011 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -29,7 +29,6 @@ mako==0.7.3 Markdown==2.2.1 networkx==1.7 nltk==2.0.4 -numpy==1.6.2 paramiko==1.9.0 path.py==3.0.1 Pillow==1.7.8 diff --git a/requirements/edx/pre.txt b/requirements/edx/pre.txt new file mode 100644 index 0000000000..a8dff9bf9a --- /dev/null +++ b/requirements/edx/pre.txt @@ -0,0 +1,3 @@ +# Numpy and scipy can't be installed in the same pip run. +# Install numpy before other things to help resolve the problem. +numpy==1.6.2 From 470569545d9e1afd2c2aaa619284e78736ccf2f0 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Wed, 22 May 2013 13:32:32 -0400 Subject: [PATCH 18/37] add a migrations to the new django_comment_common to keep things a bit more consistent with our normal useage. --- .../migrations/0001_initial.py | 92 +++++++++++++++++++ .../migrations/__init__.py | 0 .../django_comment_common/models.py | 2 + 3 files changed, 94 insertions(+) create mode 100644 common/djangoapps/django_comment_common/migrations/0001_initial.py create mode 100644 common/djangoapps/django_comment_common/migrations/__init__.py diff --git a/common/djangoapps/django_comment_common/migrations/0001_initial.py b/common/djangoapps/django_comment_common/migrations/0001_initial.py new file mode 100644 index 0000000000..f2c3ca3aee --- /dev/null +++ b/common/djangoapps/django_comment_common/migrations/0001_initial.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +from south.v2 import SchemaMigration + + +class Migration(SchemaMigration): +# +# cdodge: This is basically an empty migration since everything has - up to now - managed in the django_comment_client app +# But going forward we should be using this migration +# + def forwards(self, orm): + pass + + def backwards(self, orm): + pass + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'django_comment_common.permission': { + 'Meta': {'object_name': 'Permission'}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}), + 'roles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'permissions'", 'symmetrical': 'False', 'to': "orm['django_comment_common.Role']"}) + }, + 'django_comment_common.role': { + 'Meta': {'object_name': 'Role'}, + 'course_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'symmetrical': 'False', 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['django_comment_common'] 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/common/djangoapps/django_comment_common/models.py b/common/djangoapps/django_comment_common/models.py index 6688470753..ec722b718a 100644 --- a/common/djangoapps/django_comment_common/models.py +++ b/common/djangoapps/django_comment_common/models.py @@ -34,6 +34,7 @@ class Role(models.Model): course_id = models.CharField(max_length=255, blank=True, db_index=True) class Meta: + # use existing table that was originally created from django_comment_client app db_table = 'django_comment_client_role' def __unicode__(self): @@ -66,6 +67,7 @@ class Permission(models.Model): roles = models.ManyToManyField(Role, related_name="permissions") class Meta: + # use existing table that was originally created from django_comment_client app db_table = 'django_comment_client_permission' def __unicode__(self): From 4a929492182c99611099a44e2c3a7bdcfb8d9755 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 22 May 2013 17:05:41 -0400 Subject: [PATCH 19/37] scipy can go back into base. MySQL-python really does have to be installed after distribute has been updated by base.txt, I tested it. --- requirements/edx/base.txt | 1 + requirements/edx/post.txt | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 27d6ced011..3d9c186596 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -42,6 +42,7 @@ python-openid==2.2.5 pytz==2012h PyYAML==3.10 requests==0.14.2 +scipy==0.11.0 Shapely==1.2.16 sorl-thumbnail==11.12 South==0.7.6 diff --git a/requirements/edx/post.txt b/requirements/edx/post.txt index e1e26b381a..b637b65db0 100644 --- a/requirements/edx/post.txt +++ b/requirements/edx/post.txt @@ -1,6 +1,2 @@ - -# This must be installed after distribute 0.6.28 -MySQL-python==1.2.4c1 - -# This must be installed after numpy -scipy==0.11.0 +# This must be installed after distribute has been updated. +MySQL-python==1.2.4 From 2add58bb5aa3b003ecf3ce88c4e940541844fc67 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 20 May 2013 14:37:26 -0400 Subject: [PATCH 20/37] Create sequences for names, emails, and usernames in UserFactory --- common/djangoapps/student/tests/factories.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 9560025441..9bc80c606f 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -2,7 +2,7 @@ from student.models import (User, UserProfile, Registration, CourseEnrollmentAllowed, CourseEnrollment) from django.contrib.auth.models import Group from datetime import datetime -from factory import DjangoModelFactory, Factory, SubFactory, PostGenerationMethodCall, post_generation +from factory import DjangoModelFactory, SubFactory, PostGenerationMethodCall, post_generation, Sequence from uuid import uuid4 @@ -33,11 +33,11 @@ class RegistrationFactory(DjangoModelFactory): class UserFactory(DjangoModelFactory): FACTORY_FOR = User - username = 'robot' - email = 'robot+test@edx.org' + username = Sequence('robot{0}'.format) + email = Sequence('robot+test+{0}@edx.org'.format) password = PostGenerationMethodCall('set_password', 'test') - first_name = 'Robot' + first_name = Sequence('Robot{0}'.format) last_name = 'Test' is_staff = False is_active = True From dcefd9e9910efae29016599dd6d39548fa49e7fb Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 20 May 2013 14:47:44 -0400 Subject: [PATCH 21/37] Taught rake test_lms and test_cms to run specific tests by passing a single argument --- doc/testing.md | 5 ++--- jenkins/test.sh | 4 ++-- rakefiles/tests.rake | 15 ++++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/testing.md b/doc/testing.md index d6c7b7ee86..8ff98153f1 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -117,12 +117,11 @@ xmodule can be tested independently, with this: To run a single django test class: - django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/courseware/tests/tests.py:TestViewAuth + rake test_lms[courseware.tests.tests:testViewAuth] To run a single django test: - django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/courseware/tests/tests.py:TestViewAuth.test_dark_launch - + rake test_lms[courseware.tests.tests:TestViewAuth.test_dark_launch] To run a single nose test file: diff --git a/jenkins/test.sh b/jenkins/test.sh index d8cd2c1843..b53d54dccf 100755 --- a/jenkins/test.sh +++ b/jenkins/test.sh @@ -73,8 +73,8 @@ rake pylint > pylint.log || cat pylint.log TESTS_FAILED=0 # Run the python unit tests -rake test_cms[false] || TESTS_FAILED=1 -rake test_lms[false] || TESTS_FAILED=1 +rake test_cms || TESTS_FAILED=1 +rake test_lms || TESTS_FAILED=1 rake test_common/lib/capa || TESTS_FAILED=1 rake test_common/lib/xmodule || TESTS_FAILED=1 diff --git a/rakefiles/tests.rake b/rakefiles/tests.rake index ebe8ea6375..d745579ada 100644 --- a/rakefiles/tests.rake +++ b/rakefiles/tests.rake @@ -12,10 +12,11 @@ def run_under_coverage(cmd, root) return cmd end -def run_tests(system, report_dir, stop_on_failure=true) +def run_tests(system, report_dir, test_id=nil, stop_on_failure=true) ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml") dirs = Dir["common/djangoapps/*"] + Dir["#{system}/djangoapps/*"] - cmd = django_admin(system, :test, 'test', '--logging-clear-handlers', *dirs.each) + test_id = dirs.join(' ') if test_id.nil? or test_id == '' + cmd = django_admin(system, :test, 'test', '--logging-clear-handlers', test_id) sh(run_under_coverage(cmd, system)) do |ok, res| if !ok and stop_on_failure abort "Test failed!" @@ -44,13 +45,13 @@ TEST_TASK_DIRS = [] # Per System tasks desc "Run all django tests on our djangoapps for the #{system}" - task "test_#{system}", [:stop_on_failure] => ["clean_test_files", :predjango, "#{system}:gather_assets:test", "fasttest_#{system}"] + task "test_#{system}", [:test_id, :stop_on_failure] => ["clean_test_files", :predjango, "#{system}:gather_assets:test", "fasttest_#{system}"] # Have a way to run the tests without running collectstatic -- useful when debugging without # messing with static files. - task "fasttest_#{system}", [:stop_on_failure] => [report_dir, :install_prereqs, :predjango] do |t, args| - args.with_defaults(:stop_on_failure => 'true') - run_tests(system, report_dir, args.stop_on_failure) + task "fasttest_#{system}", [:test_id, :stop_on_failure] => [report_dir, :install_prereqs, :predjango] do |t, args| + args.with_defaults(:stop_on_failure => 'true', :test_id => nil) + run_tests(system, report_dir, args.test_id, args.stop_on_failure) end # Run acceptance tests @@ -100,7 +101,7 @@ end task :test do TEST_TASK_DIRS.each do |dir| - Rake::Task["test_#{dir}"].invoke(false) + Rake::Task["test_#{dir}"].invoke(nil, false) end if $failed_tests > 0 From a282153337743119edd1f1be0e106e9f9644e67b Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 22 May 2013 21:38:03 -0400 Subject: [PATCH 22/37] Cleaned up whitespace in docs --- doc/testing.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/testing.md b/doc/testing.md index 8ff98153f1..c334317de7 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -8,7 +8,7 @@ and acceptance tests. ### Unit Tests * Each test case should be concise: setup, execute, check, and teardown. -If you find yourself writing tests with many steps, consider refactoring +If you find yourself writing tests with many steps, consider refactoring the unit under tests into smaller units, and then testing those individually. * As a rule of thumb, your unit tests should cover every code branch. @@ -16,19 +16,19 @@ the unit under tests into smaller units, and then testing those individually. * Mock or patch external dependencies. We use [voidspace mock](http://www.voidspace.org.uk/python/mock/). -* We unit test Python code (using [unittest](http://docs.python.org/2/library/unittest.html)) and +* We unit test Python code (using [unittest](http://docs.python.org/2/library/unittest.html)) and Javascript (using [Jasmine](http://pivotal.github.io/jasmine/)) ### Integration Tests * Test several units at the same time. Note that you can still mock or patch dependencies -that are not under test! For example, you might test that -`LoncapaProblem`, `NumericalResponse`, and `CorrectMap` in the +that are not under test! For example, you might test that +`LoncapaProblem`, `NumericalResponse`, and `CorrectMap` in the `capa` package work together, while still mocking out template rendering. * Use integration tests to ensure that units are hooked up correctly. -You do not need to test every possible input--that's what unit -tests are for. Instead, focus on testing the "happy path" +You do not need to test every possible input--that's what unit +tests are for. Instead, focus on testing the "happy path" to verify that the components work together correctly. * Many of our tests use the [Django test client](https://docs.djangoproject.com/en/dev/topics/testing/overview/) to simulate @@ -43,8 +43,8 @@ these tests simulate user interactions through the browser using Overall, you want to write the tests that **maximize coverage** while **minimizing maintenance**. -In practice, this usually means investing heavily -in unit tests, which tend to be the most robust to changes in the code base. +In practice, this usually means investing heavily +in unit tests, which tend to be the most robust to changes in the code base. ![Test Pyramid](test_pyramid.png) @@ -53,13 +53,13 @@ and acceptance tests. Most of our tests are unit tests or integration tests. ## Test Locations -* Python unit and integration tests: Located in +* Python unit and integration tests: Located in subpackages called `tests`. -For example, the tests for the `capa` package are located in +For example, the tests for the `capa` package are located in `common/lib/capa/capa/tests`. * Javascript unit tests: Located in `spec` folders. For example, -`common/lib/xmodule/xmodule/js/spec` and `{cms,lms}/static/coffee/spec` +`common/lib/xmodule/xmodule/js/spec` and `{cms,lms}/static/coffee/spec` For consistency, you should use the same directory structure for implementation and test. For example, the test for `src/views/module.coffee` should be written in `spec/views/module_spec.coffee`. @@ -101,7 +101,7 @@ You can run tests using `rake` commands. For example, rake test -runs all the tests. It also runs `collectstatic`, which prepares the static files used by the site (for example, compiling Coffeescript to Javascript). +runs all the tests. It also runs `collectstatic`, which prepares the static files used by the site (for example, compiling Coffeescript to Javascript). You can also run the tests without `collectstatic`, which tends to be faster: @@ -149,7 +149,7 @@ If the `phantomjs` binary is not on the path, set the `PHANTOMJS_PATH` environme PHANTOMJS_PATH=/path/to/phantomjs rake phantomjs_jasmine_{lms,cms} -Once you have run the `rake` command, your browser should open to +Once you have run the `rake` command, your browser should open to to `http://localhost/_jasmine/`, which displays the test results. **Troubleshooting**: If you get an error message while running the `rake` task, @@ -162,7 +162,7 @@ Most of our tests use [Splinter](http://splinter.cobrateam.info/) to simulate UI browser interactions. Splinter, in turn, uses [Selenium](http://docs.seleniumhq.org/) to control the Chrome browser. -**Prerequisite**: You must have [ChromeDriver](https://code.google.com/p/selenium/wiki/ChromeDriver) +**Prerequisite**: You must have [ChromeDriver](https://code.google.com/p/selenium/wiki/ChromeDriver) installed to run the tests in Chrome. The tests are confirmed to run with Chrome (not Chromium) version 26.0.0.1410.63 with ChromeDriver version r195636. @@ -189,7 +189,7 @@ Try running: pip install -r requirements.txt -**Note**: The acceptance tests can *not* currently run in parallel. +**Note**: The acceptance tests can *not* currently run in parallel. ## Viewing Test Coverage From 7ef6132194ccd207c554521209ba3472bf523940 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 22 May 2013 21:34:07 -0400 Subject: [PATCH 23/37] Make factories return unicode data Django always returns string data from the database as unicode objects (ref: https://docs.djangoproject.com/en/1.4/ref/unicode/#models). Therefor, our factories should do the same --- common/djangoapps/student/tests/factories.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 9bc80c606f..d34f554fbf 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -9,35 +9,35 @@ from uuid import uuid4 class GroupFactory(DjangoModelFactory): FACTORY_FOR = Group - name = 'staff_MITx/999/Robot_Super_Course' + name = u'staff_MITx/999/Robot_Super_Course' class UserProfileFactory(DjangoModelFactory): FACTORY_FOR = UserProfile user = None - name = 'Robot Test' + name = u'Robot Test' level_of_education = None - gender = 'm' + gender = u'm' mailing_address = None - goals = 'World domination' + goals = u'World domination' class RegistrationFactory(DjangoModelFactory): FACTORY_FOR = Registration user = None - activation_key = uuid4().hex + activation_key = uuid4().hex.decode('ascii') class UserFactory(DjangoModelFactory): FACTORY_FOR = User - username = Sequence('robot{0}'.format) - email = Sequence('robot+test+{0}@edx.org'.format) + username = Sequence(u'robot{0}'.format) + email = Sequence(u'robot+test+{0}@edx.org'.format) password = PostGenerationMethodCall('set_password', 'test') - first_name = Sequence('Robot{0}'.format) + first_name = Sequence(u'Robot{0}'.format) last_name = 'Test' is_staff = False is_active = True @@ -64,7 +64,7 @@ class CourseEnrollmentFactory(DjangoModelFactory): FACTORY_FOR = CourseEnrollment user = SubFactory(UserFactory) - course_id = 'edX/toy/2012_Fall' + course_id = u'edX/toy/2012_Fall' class CourseEnrollmentAllowedFactory(DjangoModelFactory): From 293d0a8645e348ff9ab0f7fa57da7067c7ff03fa Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Wed, 22 May 2013 21:58:29 -0400 Subject: [PATCH 24/37] special case the db setup for the cms acceptance tests. We need the comment client database tables to be created since we're creating the DB from scratch --- rakefiles/tests.rake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rakefiles/tests.rake b/rakefiles/tests.rake index ebe8ea6375..2dbde6b692 100644 --- a/rakefiles/tests.rake +++ b/rakefiles/tests.rake @@ -25,6 +25,16 @@ def run_tests(system, report_dir, stop_on_failure=true) end def run_acceptance_tests(system, report_dir, harvest_args) + # HACK: Since now the CMS depends on the existence of some database tables + # that used to be in LMS (Role/Permissions for Forums) we need to make + # sure the acceptance tests create/migrate the database tables + # that are represented in the LMS. We might be able to address this by moving + # out the migrations from lms/django_comment_client, but then we'd have to + # repair all the existing migrations from the upgrade tables in the DB. + if system == :cms + sh(django_admin('lms', 'acceptance', 'syncdb', '--noinput')) + sh(django_admin('lms', 'acceptance', 'migrate', '--noinput')) + end sh(django_admin(system, 'acceptance', 'syncdb', '--noinput')) sh(django_admin(system, 'acceptance', 'migrate', '--noinput')) sh(django_admin(system, 'acceptance', 'harvest', '--debug-mode', '--tag -skip', harvest_args)) From b2b6ca5f7ba5acd89bb8e2e0b4704db6d1edc2fa Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 15 May 2013 13:10:52 -0400 Subject: [PATCH 25/37] Record junit reports of jasmine tests --- cms/envs/jasmine.py | 7 ++- common/lib/capa/jasmine_test_runner.html.erb | 48 ------------------- common/templates/jasmine/base.html | 8 ++-- .../jasmine/jasmine_test_runner.html.erb | 29 ++--------- common/test/phantom-jasmine | 1 - jenkins/test.sh | 2 +- lms/envs/jasmine.py | 7 ++- package.json | 3 +- rakefiles/jasmine.rake | 33 ++++++------- requirements/edx/base.txt | 2 + 10 files changed, 41 insertions(+), 99 deletions(-) delete mode 100644 common/lib/capa/jasmine_test_runner.html.erb delete mode 160000 common/test/phantom-jasmine diff --git a/cms/envs/jasmine.py b/cms/envs/jasmine.py index f3a982aa43..a4b8292d71 100644 --- a/cms/envs/jasmine.py +++ b/cms/envs/jasmine.py @@ -36,8 +36,13 @@ PIPELINE_JS['spec'] = { } JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' +JASMINE_REPORT_DIR = os.environ.get('JASMINE_REPORT_DIR', 'reports/cms/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') # Remove the localization middleware class because it requires the test database # to be sync'd and migrated in order to run the jasmine tests interactively @@ -45,4 +50,4 @@ STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib') MIDDLEWARE_CLASSES = tuple(e for e in MIDDLEWARE_CLASSES \ if e != 'django.middleware.locale.LocaleMiddleware') -INSTALLED_APPS += ('django_jasmine', ) +INSTALLED_APPS += ('django_jasmine', 'settings_context_processor') diff --git a/common/lib/capa/jasmine_test_runner.html.erb b/common/lib/capa/jasmine_test_runner.html.erb deleted file mode 100644 index 7b078daedd..0000000000 --- a/common/lib/capa/jasmine_test_runner.html.erb +++ /dev/null @@ -1,48 +0,0 @@ - - - - Jasmine Test Runner - - - - - - - - - - - - - - - - - - - <% 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 26/37] 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 27/37] 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 28/37] 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 29/37] 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 30/37] 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 31/37] 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 32/37] 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 33/37] 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 34/37] 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 35/37] 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 36/37] 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 37/37] 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