From 759be4d13d70a35ad751956d7965ce19e1ec3624 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 10 Aug 2012 14:08:58 -0400 Subject: [PATCH 01/17] added spinner gif --- common/lib/xmodule/xmodule/css/capa/display.scss | 13 +++++++++++++ common/static/images/spinner.gif | Bin 0 -> 6942 bytes 2 files changed, 13 insertions(+) create mode 100644 common/static/images/spinner.gif diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 2088e8baa3..560df2a178 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -77,6 +77,19 @@ div { } } + &.checking { + p.status { + @include inline-block(); + background: url('../images/spinner.gif') center center no-repeat; + height: 20px; + width: 20px; + } + + input { + border-color: green; + } + } + &.incorrect, &.ui-icon-close { p.status { @include inline-block(); diff --git a/common/static/images/spinner.gif b/common/static/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..b2f94cd12c351160f0a5ec8aa0a9b7f7f2e16d81 GIT binary patch literal 6942 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 c+Urdh#iqlzMkC{Kbk4KSn`>Ip Date: Fri, 10 Aug 2012 14:17:59 -0400 Subject: [PATCH 02/17] changed field border color --- common/lib/xmodule/xmodule/css/capa/display.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 560df2a178..6ef0d40176 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -77,7 +77,7 @@ div { } } - &.checking { + &.processing { p.status { @include inline-block(); background: url('../images/spinner.gif') center center no-repeat; @@ -86,7 +86,7 @@ div { } input { - border-color: green; + border-color: #aaa; } } From 52c49f2b9a49cb8648ed14dd7e703bebfc05324e Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 10 Aug 2012 15:48:58 -0400 Subject: [PATCH 03/17] added a cleaner arrow implementation --- lms/static/sass/multicourse/_dashboard.scss | 23 ++++++++------------- lms/templates/dashboard.html | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 9c2b71f5c0..9581f5e016 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -203,28 +203,23 @@ display: block; left: 0px; position: absolute; + z-index: 50; top: 0px; @include transition(all, 0.15s, linear); right: 0px; } .arrow { - border-top: 8px solid; - border-left: 8px solid; - border-color: rgba(0,0,0, 0.7); - @include box-shadow(inset 0 1px 0 0 rgba(255,255,255, 0.8), -1px 0 1px 0 rgba(255,255,255, 0.8)); - content: ""; - display: block; - height: 55px; - left: 50%; - margin-left: -10px; - margin-top: -30px; - opacity: 0; position: absolute; - top: 50%; - @include transform(rotate(-45deg)); + z-index: 100; + width: 100%; + font-size: 70px; + line-height: 110px; + text-align: center; + text-decoration: none; + color: rgba(0, 0, 0, .7); + opacity: 0; @include transition(all, 0.15s, linear); - width: 55px; } &:hover { diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 480568a5b9..fc8e9abf30 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -73,7 +73,7 @@ %>
-
+
From f4db9c01990267f8b174184e5ae82947f0aa919b Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 10 Aug 2012 15:54:00 -0400 Subject: [PATCH 04/17] added additional processing class --- common/lib/xmodule/xmodule/css/capa/display.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 6ef0d40176..7832d95cb4 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -147,6 +147,15 @@ div { width: 14px; } + &.processing, &.ui-icon-processing { + @include inline-block(); + background: url('../images/spinner.gif') center center no-repeat; + height: 20px; + position: relative; + top: 6px; + width: 25px; + } + &.correct, &.ui-icon-check { @include inline-block(); background: url('../images/correct-icon.png') center center no-repeat; From 8b79d811d4454abbdf7605be63558101685ad2f6 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 10 Aug 2012 14:08:58 -0400 Subject: [PATCH 05/17] added spinner gif --- common/lib/xmodule/xmodule/css/capa/display.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 6b1c32ae65..d209f2d157 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -89,6 +89,19 @@ div { } } + &.checking { + p.status { + @include inline-block(); + background: url('../images/spinner.gif') center center no-repeat; + height: 20px; + width: 20px; + } + + input { + border-color: green; + } + } + &.incorrect, &.ui-icon-close { p.status { @include inline-block(); From 4eefa73336f54961a5373c671b104bee09e6632c Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 10 Aug 2012 14:17:59 -0400 Subject: [PATCH 06/17] changed field border color --- common/lib/xmodule/xmodule/css/capa/display.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index d209f2d157..9a5aa82f6f 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -89,7 +89,7 @@ div { } } - &.checking { + &.processing { p.status { @include inline-block(); background: url('../images/spinner.gif') center center no-repeat; @@ -98,7 +98,7 @@ div { } input { - border-color: green; + border-color: #aaa; } } From ee0cbf66f9d458baefc96263ad6120209569f56b Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 10 Aug 2012 15:48:58 -0400 Subject: [PATCH 07/17] added a cleaner arrow implementation --- lms/static/sass/multicourse/_dashboard.scss | 23 ++++++++------------- lms/templates/dashboard.html | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 9c2b71f5c0..9581f5e016 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -203,28 +203,23 @@ display: block; left: 0px; position: absolute; + z-index: 50; top: 0px; @include transition(all, 0.15s, linear); right: 0px; } .arrow { - border-top: 8px solid; - border-left: 8px solid; - border-color: rgba(0,0,0, 0.7); - @include box-shadow(inset 0 1px 0 0 rgba(255,255,255, 0.8), -1px 0 1px 0 rgba(255,255,255, 0.8)); - content: ""; - display: block; - height: 55px; - left: 50%; - margin-left: -10px; - margin-top: -30px; - opacity: 0; position: absolute; - top: 50%; - @include transform(rotate(-45deg)); + z-index: 100; + width: 100%; + font-size: 70px; + line-height: 110px; + text-align: center; + text-decoration: none; + color: rgba(0, 0, 0, .7); + opacity: 0; @include transition(all, 0.15s, linear); - width: 55px; } &:hover { diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 480568a5b9..fc8e9abf30 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -73,7 +73,7 @@ %>
-
+
From 7b4cd3e4b345586fa569d7844e3ee9d14536be4c Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Fri, 10 Aug 2012 15:54:00 -0400 Subject: [PATCH 08/17] added additional processing class --- common/lib/xmodule/xmodule/css/capa/display.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 9a5aa82f6f..6a9b57cfef 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -159,7 +159,7 @@ div { width: 14px; } - &.processing, &.ui-icon-check { + &.processing, &.ui-icon-processing { @include inline-block(); background: url('../images/spinner.gif') center center no-repeat; height: 20px; From b5beb13964210089d5411bf129541c90acace9e5 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 13 Aug 2012 13:06:35 -0400 Subject: [PATCH 09/17] Fix side-effect related problems with User replication. 1. Multiple save()s on the same model are now handled properly. We had to unmark model objects after the appropriate signals had fired. 2. There was a side-effect where we were saving the portal User object to the course_db with the using kw param, but models remember where they were last saved to, so a later save on that model object would go to the wrong database. --- common/djangoapps/student/models.py | 48 ++++++++++++++--------------- common/djangoapps/student/tests.py | 29 +++++++++++------ 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 0fbe70c0b3..a3f6926a51 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -255,10 +255,13 @@ def add_user_to_default_group(user, group): utg.save() ########################## REPLICATION SIGNALS ################################# -@receiver(post_save, sender=User) +#@receiver(post_save, sender=User) def replicate_user_save(sender, **kwargs): - user_obj = kwargs['instance'] - return replicate_model(User.save, user_obj, user_obj.id) + user_obj = kwargs['instance'] + if not should_replicate(user_obj): + return + for course_db_name in db_names_to_replicate_to(user_obj.id): + replicate_user(user_obj, course_db_name) @receiver(post_save, sender=CourseEnrollment) def replicate_enrollment_save(sender, **kwargs): @@ -287,8 +290,8 @@ def replicate_enrollment_save(sender, **kwargs): @receiver(post_delete, sender=CourseEnrollment) def replicate_enrollment_delete(sender, **kwargs): - enrollment_obj = kwargs['instance'] - return replicate_model(CourseEnrollment.delete, enrollment_obj, enrollment_obj.user_id) + enrollment_obj = kwargs['instance'] + return replicate_model(CourseEnrollment.delete, enrollment_obj, enrollment_obj.user_id) @receiver(post_save, sender=UserProfile) def replicate_userprofile_save(sender, **kwargs): @@ -311,23 +314,20 @@ def replicate_user(portal_user, course_db_name): overridden. """ try: - # If the user exists in the Course DB, update the appropriate fields and - # save it back out to the Course DB. course_user = User.objects.using(course_db_name).get(id=portal_user.id) - for field in USER_FIELDS_TO_COPY: - setattr(course_user, field, getattr(portal_user, field)) - - mark_handled(course_user) log.debug("User {0} found in Course DB, replicating fields to {1}" .format(course_user, course_db_name)) - course_user.save(using=course_db_name) # Just being explicit. - except User.DoesNotExist: - # Otherwise, just make a straight copy to the Course DB. - mark_handled(portal_user) log.debug("User {0} not found in Course DB, creating copy in {1}" .format(portal_user, course_db_name)) - portal_user.save(using=course_db_name) + course_user = User() + + for field in USER_FIELDS_TO_COPY: + setattr(course_user, field, getattr(portal_user, field)) + + mark_handled(course_user) + course_user.save(using=course_db_name) # Just being explicit. + unmark(course_user) def replicate_model(model_method, instance, user_id): """ @@ -337,13 +337,14 @@ def replicate_model(model_method, instance, user_id): if not should_replicate(instance): return - mark_handled(instance) course_db_names = db_names_to_replicate_to(user_id) log.debug("Replicating {0} for user {1} to DBs: {2}" .format(model_method, user_id, course_db_names)) + mark_handled(instance) for db_name in course_db_names: model_method(instance, using=db_name) + unmark(instance) ######### Replication Helpers ######### @@ -371,7 +372,7 @@ def db_names_to_replicate_to(user_id): def marked_handled(instance): """Have we marked this instance as being handled to avoid infinite loops caused by saving models in post_save hooks for the same models?""" - return hasattr(instance, '_do_not_copy_to_course_db') + return hasattr(instance, '_do_not_copy_to_course_db') and instance._do_not_copy_to_course_db def mark_handled(instance): """You have to mark your instance with this function or else we'll go into @@ -384,6 +385,11 @@ def mark_handled(instance): """ instance._do_not_copy_to_course_db = True +def unmark(instance): + """If we don't unmark a model after we do replication, then consecutive + save() calls won't be properly replicated.""" + instance._do_not_copy_to_course_db = False + def should_replicate(instance): """Should this instance be replicated? We need to be a Portal server and the instance has to not have been marked_handled.""" @@ -398,9 +404,3 @@ def should_replicate(instance): return False return True - - - - - - diff --git a/common/djangoapps/student/tests.py b/common/djangoapps/student/tests.py index ad7ddb70d1..b71fce5158 100644 --- a/common/djangoapps/student/tests.py +++ b/common/djangoapps/student/tests.py @@ -4,6 +4,7 @@ when you run "manage.py test". Replace this with more appropriate tests for your application. """ +import logging from datetime import datetime from django.test import TestCase @@ -13,6 +14,8 @@ from .models import User, UserProfile, CourseEnrollment, replicate_user, USER_FI COURSE_1 = 'edX/toy/2012_Fall' COURSE_2 = 'edx/full/6.002_Spring_2012' +log = logging.getLogger(__name__) + class ReplicationTest(TestCase): multi_db = True @@ -47,23 +50,18 @@ class ReplicationTest(TestCase): field, portal_user, course_user )) - if hasattr(portal_user, 'seen_response_count'): - # Since it's the first copy over of User data, we should have all of it - self.assertEqual(portal_user.seen_response_count, - course_user.seen_response_count) - - # But if we replicate again, the user already exists in the Course DB, - # so it shouldn't update the seen_response_count (which is Askbot - # controlled). # This hasattr lameness is here because we don't want this test to be # triggered when we're being run by CMS tests (Askbot doesn't exist # there, so the test will fail). + # + # seen_response_count isn't a field we care about, so it shouldn't have + # been copied over. if hasattr(portal_user, 'seen_response_count'): portal_user.seen_response_count = 20 replicate_user(portal_user, COURSE_1) course_user = User.objects.using(COURSE_1).get(id=portal_user.id) self.assertEqual(portal_user.seen_response_count, 20) - self.assertEqual(course_user.seen_response_count, 10) + self.assertEqual(course_user.seen_response_count, 0) # Another replication should work for an email change however, since # it's a field we care about. @@ -123,6 +121,19 @@ class ReplicationTest(TestCase): UserProfile.objects.using(COURSE_2).get, id=portal_user_profile.id) + log.debug("Make sure our seen_response_count is not replicated.") + if hasattr(portal_user, 'seen_response_count'): + portal_user.seen_response_count = 200 + course_user = User.objects.using(COURSE_1).get(id=portal_user.id) + self.assertEqual(portal_user.seen_response_count, 200) + self.assertEqual(course_user.seen_response_count, 0) + portal_user.save() + + course_user = User.objects.using(COURSE_1).get(id=portal_user.id) + self.assertEqual(portal_user.seen_response_count, 200) + self.assertEqual(course_user.seen_response_count, 0) + + def test_enrollment_for_user_info_after_enrollment(self): """Test the effect of modifying User data after you've enrolled.""" From 301695a5c4e6913ff2e15529be12941d8e3d34a1 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 13 Aug 2012 13:11:27 -0400 Subject: [PATCH 10/17] Remove outdated comment --- common/djangoapps/student/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index a3f6926a51..d04a56362e 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -326,7 +326,7 @@ def replicate_user(portal_user, course_db_name): setattr(course_user, field, getattr(portal_user, field)) mark_handled(course_user) - course_user.save(using=course_db_name) # Just being explicit. + course_user.save(using=course_db_name) unmark(course_user) def replicate_model(model_method, instance, user_id): From a25f289ca7d14bbdb3bf3c6a62fd02b694c40d8c Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 13 Aug 2012 13:16:33 -0400 Subject: [PATCH 11/17] re-enable User save signal handler --- common/djangoapps/student/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index d04a56362e..6d1cbb5afb 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -255,7 +255,7 @@ def add_user_to_default_group(user, group): utg.save() ########################## REPLICATION SIGNALS ################################# -#@receiver(post_save, sender=User) +@receiver(post_save, sender=User) def replicate_user_save(sender, **kwargs): user_obj = kwargs['instance'] if not should_replicate(user_obj): From c4d89cd535cc9afa04a719e23d83bc35527033bc Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 13 Aug 2012 13:34:24 -0400 Subject: [PATCH 12/17] One more test to make sure users are really being copied --- common/djangoapps/student/tests.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/djangoapps/student/tests.py b/common/djangoapps/student/tests.py index b71fce5158..b33678fbac 100644 --- a/common/djangoapps/student/tests.py +++ b/common/djangoapps/student/tests.py @@ -133,6 +133,12 @@ class ReplicationTest(TestCase): self.assertEqual(portal_user.seen_response_count, 200) self.assertEqual(course_user.seen_response_count, 0) + portal_user.email = 'jim@edx.org' + portal_user.save() + course_user = User.objects.using(COURSE_1).get(id=portal_user.id) + self.assertEqual(portal_user.email, 'jim@edx.org') + self.assertEqual(course_user.email, 'jim@edx.org') + def test_enrollment_for_user_info_after_enrollment(self): From 706fd99cabcd6d36e5f525b0522459b1571c844e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 13 Aug 2012 14:21:19 -0400 Subject: [PATCH 13/17] Address comments on #394 --- common/lib/xmodule/xmodule/x_module.py | 4 ++-- lms/djangoapps/courseware/grades.py | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 9f99f5a526..071e453901 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -227,7 +227,7 @@ class XModule(HTMLSnippet): def get_display_items(self): ''' Returns a list of descendent module instances that will display - immediately inside this module + immediately inside this module. ''' items = [] for child in self.get_children(): @@ -238,7 +238,7 @@ class XModule(HTMLSnippet): def displayable_items(self): ''' Returns list of displayable modules contained by this module. If this - module is visible, should return [self] + module is visible, should return [self]. ''' return [self] diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py index 85f883d2f0..aa160ee22a 100644 --- a/lms/djangoapps/courseware/grades.py +++ b/lms/djangoapps/courseware/grades.py @@ -145,15 +145,11 @@ def progress_summary(student, course, grader, student_module_cache): instance_modules for the student """ chapters = [] - for c in course.get_children(): - # Don't include chapters that aren't displayable (e.g. due to error) - if c not in c.displayable_items(): - continue + # Don't include chapters that aren't displayable (e.g. due to error) + for c in course.get_display_items(): sections = [] - for s in c.get_children(): + for s in c.get_display_items(): # Same for sections - if s not in s.displayable_items(): - continue graded = s.metadata.get('graded', False) scores = [] for module in yield_module_descendents(s): From add050593af2dc0b14010c5876bfc3004c826786 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 13 Aug 2012 14:23:41 -0400 Subject: [PATCH 14/17] address comment on #392 --- common/djangoapps/static_replace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/static_replace.py b/common/djangoapps/static_replace.py index ba3cfb302a..ce3dc55031 100644 --- a/common/djangoapps/static_replace.py +++ b/common/djangoapps/static_replace.py @@ -17,8 +17,8 @@ def try_staticfiles_lookup(path): except Exception as err: log.warning("staticfiles_storage couldn't find path {}: {}".format( path, str(err))) - # Just return a dead link--don't kill everything. - url = "file_not_found" + # Just return the original path; don't kill everything. + url = path return url From 8716f88155ad253ebff573415f5cca1e6126a3ad Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 13 Aug 2012 15:03:46 -0400 Subject: [PATCH 15/17] add course enrollment windows * if the course metadata have enrollment_start and/or enrollment_end, only allow normal users to enroll post start and pre end. * If DARK_LAUNCH is on, staff can enroll outside the window --- common/djangoapps/student/views.py | 57 +++++++-- common/lib/xmodule/xmodule/course_module.py | 27 +++- lms/djangoapps/courseware/tests/tests.py | 129 +++++++++++++++++--- 3 files changed, 177 insertions(+), 36 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 1951426ea7..d9720903d3 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -1,13 +1,14 @@ import datetime +import feedparser +import itertools import json import logging import random import string import sys -import uuid -import feedparser +import time import urllib -import itertools +import uuid from django.conf import settings from django.contrib.auth import logout, authenticate, login @@ -26,17 +27,19 @@ from bs4 import BeautifulSoup from django.core.cache import cache from django_future.csrf import ensure_csrf_cookie -from student.models import Registration, UserProfile, PendingNameChange, PendingEmailChange, CourseEnrollment +from student.models import (Registration, UserProfile, + PendingNameChange, PendingEmailChange, + CourseEnrollment) from util.cache import cache_if_anonymous from xmodule.course_module import CourseDescriptor from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError -from models import Registration, UserProfile, PendingNameChange, PendingEmailChange, CourseEnrollment from datetime import date from collections import namedtuple -from courseware.courses import course_staff_group_name, has_staff_access_to_course, get_courses_by_university +from courseware.courses import (course_staff_group_name, has_staff_access_to_course, + get_courses_by_university) log = logging.getLogger("mitx.student") Article = namedtuple('Article', 'title url author image deck publication publish_date') @@ -47,7 +50,8 @@ def csrf_token(context): csrf_token = context.get('csrf_token', '') if csrf_token == 'NOTPROVIDED': return '' - return u'
' % (csrf_token) + return (u'
' % (csrf_token)) @ensure_csrf_cookie @@ -162,6 +166,26 @@ def change_enrollment_view(request): """Delegate to change_enrollment to actually do the work.""" return HttpResponse(json.dumps(change_enrollment(request))) +def enrollment_allowed(user, course): + """If the course has an enrollment period, check whether we are in it. + Also respects the DARK_LAUNCH setting""" + now = time.gmtime() + start = course.enrollment_start + end = course.enrollment_end + + if (start is None or now > start) and (end is None or now < end): + # in enrollment period. + print "allowing enrollment in {}: start {}, end {}, now {}".format( + course.location.url(), start, end, now) + return True + + if settings.MITX_FEATURES['DARK_LAUNCH']: + if has_staff_access_to_course(user, course): + # if dark launch, staff can enroll outside enrollment window + return True + return False + + def change_enrollment(request): if request.method != "POST": raise Http404 @@ -174,7 +198,8 @@ def change_enrollment(request): course_id = request.POST.get("course_id", None) if course_id == None: - return HttpResponse(json.dumps({'success': False, 'error': 'There was an error receiving the course id.'})) + return HttpResponse(json.dumps({'success': False, + 'error': 'There was an error receiving the course id.'})) if action == "enroll": # Make sure the course exists @@ -187,12 +212,20 @@ def change_enrollment(request): return {'success': False, 'error': 'The course requested does not exist.'} if settings.MITX_FEATURES.get('ACCESS_REQUIRE_STAFF_FOR_COURSE'): - # require that user be in the staff_* group (or be an overall admin) to be able to enroll - # eg staff_6.002x or staff_6.00x + # require that user be in the staff_* group (or be an + # overall admin) to be able to enroll eg staff_6.002x or + # staff_6.00x if not has_staff_access_to_course(user, course): staff_group = course_staff_group_name(course) - log.debug('user %s denied enrollment to %s ; not in %s' % (user,course.location.url(),staff_group)) - return {'success': False, 'error' : '%s membership required to access course.' % staff_group} + log.debug('user %s denied enrollment to %s ; not in %s' % ( + user, course.location.url(), staff_group)) + return {'success': False, + 'error' : '%s membership required to access course.' % staff_group} + + if not enrollment_allowed(user, course): + return {'success': False, + 'error': 'enrollment in {} not allowed at this time' + .format(course.display_name)} enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) return {'success': True} diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 7ec9f54cd2..e7d480f4e9 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -21,18 +21,35 @@ class CourseDescriptor(SequenceDescriptor): try: self.start = time.strptime(self.metadata["start"], "%Y-%m-%dT%H:%M") except KeyError: - self.start = time.gmtime(0) #The epoch msg = "Course loaded without a start date. id = %s" % self.id - log.critical(msg) except ValueError as e: - self.start = time.gmtime(0) #The epoch msg = "Course loaded with a bad start date. %s '%s'" % (self.id, e) - log.critical(msg) # Don't call the tracker from the exception handler. if msg is not None: + self.start = time.gmtime(0) # The epoch + log.critical(msg) system.error_tracker(msg) + def try_parse_time(key): + """ + Parse an optional metadata key: if present, must be valid. + Return None if not present. + """ + if key in self.metadata: + try: + return time.strptime(self.metadata[key], "%Y-%m-%dT%H:%M") + except ValueError as e: + msg = "Course %s loaded with a bad metadata key %s '%s'" % ( + self.id, self.metadata[key], e) + log.warning(msg) + return None + + self.enrollment_start = try_parse_time("enrollment_start") + self.enrollment_end = try_parse_time("enrollment_end") + + + def has_started(self): return time.gmtime() > self.start @@ -100,7 +117,7 @@ class CourseDescriptor(SequenceDescriptor): for s in c.get_children(): if s.metadata.get('graded', False): xmoduledescriptors = list(yield_descriptor_descendents(s)) - + # The xmoduledescriptors included here are only the ones that have scores. section_description = { 'section_descriptor' : s, 'xmoduledescriptors' : filter(lambda child: child.has_score, xmoduledescriptors) } diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 0fb5c9983e..daffa44d2a 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -58,8 +58,22 @@ def mongo_store_config(data_dir): } } +def xml_store_config(data_dir): + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + 'eager': True, + } + } +} + + TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT -TEST_DATA_MODULESTORE = mongo_store_config(TEST_DATA_DIR) +TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) REAL_DATA_DIR = settings.GITHUB_REPO_ROOT REAL_DATA_MODULESTORE = mongo_store_config(REAL_DATA_DIR) @@ -149,8 +163,27 @@ class ActivateLoginTestCase(TestCase): class PageLoader(ActivateLoginTestCase): ''' Base class that adds a function to load all pages in a modulestore ''' + def _enroll(self, course): + """Post to the enrollment view, and return the parsed json response""" + resp = self.client.post('/change_enrollment', { + 'enrollment_action': 'enroll', + 'course_id': course.id, + }) + return parse_json(resp) + + def try_enroll(self, course): + """Try to enroll. Return bool success instead of asserting it.""" + data = self._enroll(course) + print 'Enrollment in {} result: {}'.format(course.location.url(), data) + return data['success'] + def enroll(self, course): """Enroll the currently logged-in user, and check that it worked.""" + data = self._enroll(course) + self.assertTrue(data['success']) + + def unenroll(self, course): + """Unenroll the currently logged-in user, and check that it worked.""" resp = self.client.post('/change_enrollment', { 'enrollment_action': 'enroll', 'course_id': course.id, @@ -159,6 +192,7 @@ class PageLoader(ActivateLoginTestCase): self.assertTrue(data['success']) def check_pages_load(self, course_name, data_dir, modstore): + """Make all locations in course load""" print "Checking course {0} in {1}".format(course_name, data_dir) import_from_xml(modstore, data_dir, [course_name]) @@ -191,7 +225,7 @@ class PageLoader(ActivateLoginTestCase): self.assertTrue(all_ok) -@override_settings(MODULESTORE=TEST_DATA_MODULESTORE) +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) class TestCoursesLoadTestCase(PageLoader): '''Check that all pages in test courses load properly''' @@ -207,7 +241,7 @@ class TestCoursesLoadTestCase(PageLoader): self.check_pages_load('full', TEST_DATA_DIR, modulestore()) -@override_settings(MODULESTORE=TEST_DATA_MODULESTORE) +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestViewAuth(PageLoader): """Check that view authentication works properly""" @@ -215,15 +249,15 @@ class TestViewAuth(PageLoader): # can't do imports there without manually hacking settings. def setUp(self): - print "sys.path: {}".format(sys.path) xmodule.modulestore.django._MODULESTORES = {} - modulestore().collection.drop() - import_from_xml(modulestore(), TEST_DATA_DIR, ['toy']) - import_from_xml(modulestore(), TEST_DATA_DIR, ['full']) courses = modulestore().get_courses() - # get the two courses sorted out - courses.sort(key=lambda c: c.location.course) - [self.full, self.toy] = courses + + def find_course(name): + """Assumes the course is present""" + return [c for c in courses if c.location.course==name][0] + + self.full = find_course("full") + self.toy = find_course("toy") # Create two accounts self.student = 'view@test.com' @@ -304,26 +338,35 @@ class TestViewAuth(PageLoader): self.check_for_get_code(200, url) - def test_dark_launch(self): - """Make sure that when dark launch is on, students can't access course - pages, but instructors can""" - - # test.py turns off start dates, enable them and set them correctly. - # Because settings is global, be careful not to mess it up for other tests - # (Can't use override_settings because we're only changing part of the - # MITX_FEATURES dict) + def run_wrapped(self, test): + """ + test.py turns off start dates. Enable them and DARK_LAUNCH. + Because settings is global, be careful not to mess it up for other tests + (Can't use override_settings because we're only changing part of the + MITX_FEATURES dict) + """ oldDSD = settings.MITX_FEATURES['DISABLE_START_DATES'] oldDL = settings.MITX_FEATURES['DARK_LAUNCH'] try: settings.MITX_FEATURES['DISABLE_START_DATES'] = False settings.MITX_FEATURES['DARK_LAUNCH'] = True - self._do_test_dark_launch() + test() finally: settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD settings.MITX_FEATURES['DARK_LAUNCH'] = oldDL + def test_dark_launch(self): + """Make sure that when dark launch is on, students can't access course + pages, but instructors can""" + self.run_wrapped(self._do_test_dark_launch) + + def test_enrollment_period(self): + """Check that enrollment periods work""" + self.run_wrapped(self._do_test_enrollment_period) + + def _do_test_dark_launch(self): """Actually do the test, relying on settings to be right.""" @@ -338,6 +381,7 @@ class TestViewAuth(PageLoader): self.assertTrue(settings.MITX_FEATURES['DARK_LAUNCH']) def reverse_urls(names, course): + """Reverse a list of course urls""" return [reverse(name, kwargs={'course_id': course.id}) for name in names] def dark_student_urls(course): @@ -424,6 +468,53 @@ class TestViewAuth(PageLoader): check_staff(self.toy) check_staff(self.full) + def _do_test_enrollment_period(self): + """Actually do the test, relying on settings to be right.""" + + # Make courses start in the future + tomorrow = time.time() + 24 * 3600 + nextday = tomorrow + 24 * 3600 + yesterday = time.time() - 24 * 3600 + + print "changing" + # toy course's enrollment period hasn't started + self.toy.enrollment_start = time.gmtime(tomorrow) + self.toy.enrollment_end = time.gmtime(nextday) + + # full course's has + self.full.enrollment_start = time.gmtime(yesterday) + self.full.enrollment_end = time.gmtime(tomorrow) + + print "login" + # First, try with an enrolled student + print '=== Testing student access....' + self.login(self.student, self.password) + self.assertFalse(self.try_enroll(self.toy)) + self.assertTrue(self.try_enroll(self.full)) + + print '=== Testing course instructor access....' + # Make the instructor staff in the toy course + group_name = course_staff_group_name(self.toy) + g = Group.objects.create(name=group_name) + g.user_set.add(user(self.instructor)) + + print "logout/login" + self.logout() + self.login(self.instructor, self.password) + print "Instructor should be able to enroll in toy course" + self.assertTrue(self.try_enroll(self.toy)) + + print '=== Testing staff access....' + # now make the instructor global staff, but not in the instructor group + g.user_set.remove(user(self.instructor)) + u = user(self.instructor) + u.is_staff = True + u.save() + + # unenroll and try again + self.unenroll(self.toy) + self.assertTrue(self.try_enroll(self.toy)) + @override_settings(MODULESTORE=REAL_DATA_MODULESTORE) class RealCoursesLoadTestCase(PageLoader): From 731f1cd7a8ca194ed806e37a25cd0e9d255302bc Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Mon, 13 Aug 2012 15:05:43 -0400 Subject: [PATCH 16/17] Added tooltip styles, made all buttons the same, and other small changes throughout the courseware --- .../xmodule/xmodule/css/video/display.scss | 46 ++-------- lms/static/sass/course.scss | 1 + lms/static/sass/course/_info.scss | 20 ++-- lms/static/sass/course/base/_base.scss | 1 + lms/static/sass/course/base/_extends.scss | 27 ++---- .../sass/course/courseware/_courseware.scss | 6 +- .../sass/course/courseware/_sidebar.scss | 3 +- .../sass/course/discussion/_answers.scss | 3 +- lms/static/sass/course/discussion/_forms.scss | 2 +- lms/static/sass/course/wiki/_sidebar.scss | 16 ++-- lms/static/sass/shared/_forms.scss | 92 ++++++++++--------- lms/static/sass/shared/_tooltips.scss | 12 +++ lms/templates/simplewiki/simplewiki_base.html | 6 +- 13 files changed, 105 insertions(+), 130 deletions(-) create mode 100644 lms/static/sass/shared/_tooltips.scss diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss index 235e2e3277..8d0c4ac522 100644 --- a/common/lib/xmodule/xmodule/css/video/display.scss +++ b/common/lib/xmodule/xmodule/css/video/display.scss @@ -14,7 +14,7 @@ div.video { section.video-player { height: 0; - overflow: hidden; + // overflow: hidden; padding-bottom: 56.25%; position: relative; @@ -45,12 +45,13 @@ div.video { div.slider { @extend .clearfix; background: #c2c2c2; - border: none; - border-bottom: 1px solid #000; + border: 1px solid #000; @include border-radius(0); border-top: 1px solid #000; @include box-shadow(inset 0 1px 0 #eee, 0 1px 0 #555); height: 7px; + margin-left: -1px; + margin-right: -1px; @include transition(height 2.0s ease-in-out); div.ui-widget-header { @@ -58,43 +59,12 @@ div.video { @include box-shadow(inset 0 1px 0 #999); } - .ui-tooltip.qtip .ui-tooltip-content { - background: $mit-red; - border: 1px solid darken($mit-red, 20%); - @include border-radius(2px); - @include box-shadow(inset 0 1px 0 lighten($mit-red, 10%)); - color: #fff; - font: bold 12px $body-font-family; - margin-bottom: 6px; - margin-right: 0; - overflow: visible; - padding: 4px; - text-align: center; - text-shadow: 0 -1px 0 darken($mit-red, 10%); - -webkit-font-smoothing: antialiased; - - &::after { - background: $mit-red; - border-bottom: 1px solid darken($mit-red, 20%); - border-right: 1px solid darken($mit-red, 20%); - bottom: -5px; - content: " "; - display: block; - height: 7px; - left: 50%; - margin-left: -3px; - position: absolute; - @include transform(rotate(45deg)); - width: 7px; - } - } - a.ui-slider-handle { - background: $mit-red url(../images/slider-handle.png) center center no-repeat; + background: $pink url(../images/slider-handle.png) center center no-repeat; @include background-size(50%); - border: 1px solid darken($mit-red, 20%); + border: 1px solid darken($pink, 20%); @include border-radius(15px); - @include box-shadow(inset 0 1px 0 lighten($mit-red, 10%)); + @include box-shadow(inset 0 1px 0 lighten($pink, 10%)); cursor: pointer; height: 15px; margin-left: -7px; @@ -103,7 +73,7 @@ div.video { width: 15px; &:focus, &:hover { - background-color: lighten($mit-red, 10%); + background-color: lighten($pink, 10%); outline: none; } } diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss index cc1b49a0a2..c874076a31 100644 --- a/lms/static/sass/course.scss +++ b/lms/static/sass/course.scss @@ -7,6 +7,7 @@ @import 'base/base'; @import 'base/extends'; @import 'base/animations'; +@import 'shared/tooltips'; // Course base / layout styles @import 'course/layout/courseware_subnav'; diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 1dac5354b6..1651ad4da8 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -15,15 +15,15 @@ div.info-wrapper { > ol { list-style: none; - padding-left: 0; margin-bottom: lh(); + padding-left: 0; > li { @extend .clearfix; border-bottom: 1px solid lighten($border-color, 10%); + list-style-type: disk; margin-bottom: lh(); padding-bottom: lh(.5); - list-style-type: disk; &:first-child { margin: 0 (-(lh(.5))) lh(); @@ -41,10 +41,10 @@ div.info-wrapper { h2 { float: left; - margin: 0 flex-gutter() 0 0; - width: flex-grid(2, 9); font-size: $body-font-size; font-weight: bold; + margin: 0 flex-gutter() 0 0; + width: flex-grid(2, 9); } section.update-description { @@ -68,15 +68,15 @@ div.info-wrapper { section.handouts { @extend .sidebar; - border-left: 1px solid #d3d3d3; + border-left: 1px solid $border-color; @include border-radius(0 4px 4px 0); - @include box-shadow(none); border-right: 0; + @include box-shadow(none); h1 { @extend .bottom-border; - padding: lh(.5) lh(.5); margin-bottom: 0; + padding: lh(.5) lh(.5); } ol { @@ -90,8 +90,9 @@ div.info-wrapper { &.expandable, &.collapsable { h4 { - font-weight: normal; + color: $blue; font-size: 1em; + font-weight: normal; padding: lh(.25) 0 lh(.25) lh(1.5); } } @@ -145,7 +146,8 @@ div.info-wrapper { filter: alpha(opacity=60); + h4 { - background-color: #e3e3e3; + @extend a:hover; + text-decoration: underline; } } diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index cd68b4bbaf..034e047754 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -3,6 +3,7 @@ body { } body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a { + text-align: left; font-family: $sans-serif; } diff --git a/lms/static/sass/course/base/_extends.scss b/lms/static/sass/course/base/_extends.scss index 7b3e1cba84..c5e61f593e 100644 --- a/lms/static/sass/course/base/_extends.scss +++ b/lms/static/sass/course/base/_extends.scss @@ -25,24 +25,12 @@ h1.top-header { } } -.action-link { - a { - color: $mit-red; - - &:hover { - color: darken($mit-red, 20%); - text-decoration: none; - } - } -} - .content { @include box-sizing(border-box); display: table-cell; padding: lh(); vertical-align: top; width: flex-grid(9) + flex-gutter(); - overflow: hidden; @media print { @include box-shadow(none); @@ -164,7 +152,6 @@ h1.top-header { .topbar { @extend .clearfix; border-bottom: 1px solid $border-color; - font-size: 14px; @media print { display: none; @@ -193,17 +180,17 @@ h1.top-header { h2 { display: block; - width: 700px; float: left; font-size: 0.9em; font-weight: 600; - line-height: 40px; letter-spacing: 0; - text-transform: none; - text-shadow: 0 1px 0 #fff; - white-space: nowrap; - text-overflow: ellipsis; + line-height: 40px; overflow: hidden; + text-overflow: ellipsis; + text-shadow: 0 1px 0 #fff; + text-transform: none; + white-space: nowrap; + width: 700px; .provider { font: inherit; @@ -211,4 +198,4 @@ h1.top-header { color: #6d6d6d; } } -} \ No newline at end of file +} diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index fa3e844e88..198902c146 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -146,13 +146,13 @@ div.course-wrapper { @include border-radius(0); a.ui-slider-handle { - @include box-shadow(inset 0 1px 0 lighten($mit-red, 10%)); + @include box-shadow(inset 0 1px 0 lighten($pink, 10%)); background: $mit-red url(../images/slider-bars.png) center center no-repeat; - border: 1px solid darken($mit-red, 20%); + border: 1px solid darken($pink, 20%); cursor: pointer; &:hover, &:focus { - background-color: lighten($mit-red, 10%); + background-color: lighten($pink, 10%); outline: none; } } diff --git a/lms/static/sass/course/courseware/_sidebar.scss b/lms/static/sass/course/courseware/_sidebar.scss index 7f24659533..51e9cbd90d 100644 --- a/lms/static/sass/course/courseware/_sidebar.scss +++ b/lms/static/sass/course/courseware/_sidebar.scss @@ -13,7 +13,7 @@ section.course-index { div#accordion { h3 { @include border-radius(0); - border-top: 1px solid #e3e3e3; + border-top: 1px solid lighten($border-color, 10%); font-size: em(16, 18); margin: 0; overflow: hidden; @@ -34,6 +34,7 @@ section.course-index { } &.ui-accordion-header { + border-bottom: none; color: #000; a { diff --git a/lms/static/sass/course/discussion/_answers.scss b/lms/static/sass/course/discussion/_answers.scss index f0de650206..8ab22aa833 100644 --- a/lms/static/sass/course/discussion/_answers.scss +++ b/lms/static/sass/course/discussion/_answers.scss @@ -17,7 +17,6 @@ div.answer-controls { margin-left: flex-gutter(); nav { - @extend .action-link; float: right; margin-top: 34px; @@ -144,7 +143,7 @@ div.answer-actions { text-decoration: none; &.question-delete { - // color: $mit-red; + color: $mit-red; } } } diff --git a/lms/static/sass/course/discussion/_forms.scss b/lms/static/sass/course/discussion/_forms.scss index 3d484729b1..ae02ab3b20 100644 --- a/lms/static/sass/course/discussion/_forms.scss +++ b/lms/static/sass/course/discussion/_forms.scss @@ -92,7 +92,7 @@ form.answer-form { margin-left: 2.5%; padding-left: 1.5%; border-left: 1px dashed #ddd; - color: $mit-red;; + color: $mit-red; } ul, ol, pre { diff --git a/lms/static/sass/course/wiki/_sidebar.scss b/lms/static/sass/course/wiki/_sidebar.scss index 8c9f97d27d..22574c7a5a 100644 --- a/lms/static/sass/course/wiki/_sidebar.scss +++ b/lms/static/sass/course/wiki/_sidebar.scss @@ -14,14 +14,6 @@ div#wiki_panel { } } - form { - input[type="submit"]{ - @extend .light-button; - text-transform: none; - text-shadow: none; - } - } - div#wiki_create_form { @extend .clearfix; padding: lh(.5) lh() lh(.5) 0; @@ -53,4 +45,12 @@ div#wiki_panel { } } } + + input#wiki_search_input_submit { + padding: 4px 8px; + } + + input#wiki_search_input { + margin-right: 10px; + } } diff --git a/lms/static/sass/shared/_forms.scss b/lms/static/sass/shared/_forms.scss index 760dc0bf63..842ffb0086 100644 --- a/lms/static/sass/shared/_forms.scss +++ b/lms/static/sass/shared/_forms.scss @@ -1,52 +1,54 @@ form { font-size: 1em; +} - label { - color: $base-font-color; - font: italic 300 1rem/1.6rem $serif; - margin-bottom: 5px; - text-shadow: 0 1px rgba(255,255,255, 0.4); - -webkit-font-smoothing: antialiased; +label { + color: $base-font-color; + font: italic 300 1rem/1.6rem $serif; + margin-bottom: 5px; + text-shadow: 0 1px rgba(255,255,255, 0.4); + -webkit-font-smoothing: antialiased; +} + +textarea, +input[type="text"], +input[type="email"], +input[type="password"] { + background: rgb(250,250,250); + border: 1px solid rgb(200,200,200); + @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); + font: italic 300 1rem/1.6rem $serif; + height: 35px; + padding: 5px 12px; + vertical-align: top; + -webkit-font-smoothing: antialiased; + + &:last-child { + margin-right: 0px; } - textarea, - input[type="text"], - input[type="email"], - input[type="password"] { - background: rgb(250,250,250); - border: 1px solid rgb(200,200,200); - @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); - font: italic 300 1rem/1.6rem $serif; - height: 35px; - padding: 5px 12px; - vertical-align: top; - -webkit-font-smoothing: antialiased; - - &:last-child { - margin-right: 0px; - } - - &: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)); - outline: none; - } - } - - textarea { - height: 60px; - } - - input[type="submit"] { - @include button(shiny, $blue); - @include border-radius(3px); - font: normal 1.2rem/1.6rem $sans-serif; - height: 35px; - letter-spacing: 1px; - text-transform: uppercase; - vertical-align: top; - -webkit-font-smoothing: antialiased; + &: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)); + outline: none; } } + +textarea { + height: 60px; +} + +input[type="submit"], +input[type="button"], +.button { + @include border-radius(3px); + @include button(shiny, $blue); + font: normal 1.2rem/1.6rem $sans-serif; + letter-spacing: 1px; + padding: 4px 20px; + text-transform: uppercase; + vertical-align: top; + -webkit-font-smoothing: antialiased; +} diff --git a/lms/static/sass/shared/_tooltips.scss b/lms/static/sass/shared/_tooltips.scss new file mode 100644 index 0000000000..eefbc09bef --- /dev/null +++ b/lms/static/sass/shared/_tooltips.scss @@ -0,0 +1,12 @@ +.ui-tooltip.qtip .ui-tooltip-content { + background: rgba($pink, .8); + border: 0; + color: #fff; + font: bold 12px $body-font-family; + margin-bottom: 6px; + margin-right: 0; + overflow: visible; + padding: 4px; + text-align: center; + -webkit-font-smoothing: antialiased; +} diff --git a/lms/templates/simplewiki/simplewiki_base.html b/lms/templates/simplewiki/simplewiki_base.html index 04a239b6c3..8736f3e421 100644 --- a/lms/templates/simplewiki/simplewiki_base.html +++ b/lms/templates/simplewiki/simplewiki_base.html @@ -110,7 +110,7 @@
  • - +
@@ -120,8 +120,8 @@ From 276f22b9657a1d766af47fb2871434130aaff634 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 13 Aug 2012 15:32:04 -0400 Subject: [PATCH 17/17] remove debugging print stmt --- common/djangoapps/student/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index d9720903d3..ea1770109b 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -175,8 +175,6 @@ def enrollment_allowed(user, course): if (start is None or now > start) and (end is None or now < end): # in enrollment period. - print "allowing enrollment in {}: start {}, end {}, now {}".format( - course.location.url(), start, end, now) return True if settings.MITX_FEATURES['DARK_LAUNCH']: