From 649e8b11582de101d5fe38e97dd09c53d11b3f28 Mon Sep 17 00:00:00 2001 From: Simon Chen Date: Wed, 30 Mar 2016 14:25:07 -0400 Subject: [PATCH 1/5] ECOM-3673 Update the course end date description text to remove the reference to certificate when course enrollment is in audit mode --- lms/djangoapps/courseware/date_summary.py | 6 +++++- lms/djangoapps/courseware/tests/test_date_summary.py | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index b5347d8501..4d9c1b9dc7 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -182,7 +182,11 @@ class CourseEndDate(DateSummary): @property def description(self): if datetime.now(pytz.UTC) <= self.date: - return _('To earn a certificate, you must complete all requirements before this date.') + mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) + if is_active and CourseMode.is_eligible_for_certificate(mode): + return _('To earn a certificate, you must complete all requirements before this date.') + else: + return _('After this date, course content will be archived.') return _('This course is archived, which means you can review course content but it is no longer active.') @property diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 1a6f4bb60e..8682749c70 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -173,7 +173,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): ## CourseEndDate - def test_course_end_date_during_course(self): + def test_course_end_date_for_certificate_eligible_mode(self): self.setup_course_and_user(days_till_start=-1) block = CourseEndDate(self.course, self.user) self.assertEqual( @@ -181,6 +181,14 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): 'To earn a certificate, you must complete all requirements before this date.' ) + def test_course_end_date_for_non_certificate_eligible_mode(self): + self.setup_course_and_user(days_till_start=-1, enrollment_mode=CourseMode.AUDIT) + block = CourseEndDate(self.course, self.user) + self.assertEqual( + block.description, + 'After this date, course content will be archived.' + ) + def test_course_end_date_after_course(self): self.setup_course_and_user(days_till_start=-2, days_till_end=-1) block = CourseEndDate(self.course, self.user) From 7222f9b0f585d2a72758d628fac038d39d6478d0 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Fri, 25 Mar 2016 15:55:58 -0400 Subject: [PATCH 2/5] Hide chrome and default_tab from advanced settings It doesn't make sense to list these LMS block attributes in course-level advanced settings. Doing so is confusing to course teams. --- cms/djangoapps/models/settings/course_metadata.py | 4 +++- common/test/acceptance/pages/studio/settings_advanced.py | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index ad253e1506..5859205317 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -50,7 +50,9 @@ class CourseMetadata(object): 'is_time_limited', 'is_practice_exam', 'exam_review_rules', - 'self_paced' + 'self_paced', + 'chrome', + 'default_tab', ] @classmethod diff --git a/common/test/acceptance/pages/studio/settings_advanced.py b/common/test/acceptance/pages/studio/settings_advanced.py index 10b3939c08..e209207189 100644 --- a/common/test/acceptance/pages/studio/settings_advanced.py +++ b/common/test/acceptance/pages/studio/settings_advanced.py @@ -184,9 +184,7 @@ class AdvancedSettingsPage(CoursePage): 'display_coursenumber', 'display_organization', 'catalog_visibility', - 'chrome', 'days_early_for_beta', - 'default_tab', 'disable_progress_graph', 'discussion_blackouts', 'discussion_sort_alpha', From 77a90a08ca904b7cd5dc1f72875c45c96b9e5b8f Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Fri, 25 Mar 2016 21:26:05 -0400 Subject: [PATCH 3/5] Add option to bypass course home Exposes an advanced setting in Studio allowing course teams to bypass the home tab when students arrive from the dashboard, sending them directly to course content. ECOM-3961. --- common/lib/xmodule/xmodule/course_module.py | 11 ++++++ .../xmodule/tests/test_course_module.py | 10 +++++ lms/djangoapps/courseware/tests/test_views.py | 38 +++++++++++++++++++ lms/djangoapps/courseware/views.py | 4 ++ 4 files changed, 63 insertions(+) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 35607174d3..93e1481417 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -752,6 +752,17 @@ class CourseFields(object): scope=Scope.settings ) + bypass_home = Boolean( + display_name=_("Bypass Course Home"), + help=_( + "Bypass the course home tab when students arrive from the dashboard, " + "sending them directly to course content." + ), + default=False, + scope=Scope.settings, + deprecated=True + ) + enable_subsection_gating = Boolean( display_name=_("Enable Subsection Prerequisites"), help=_( diff --git a/common/lib/xmodule/xmodule/tests/test_course_module.py b/common/lib/xmodule/xmodule/tests/test_course_module.py index ffd63fed5b..1ccb50c0fa 100644 --- a/common/lib/xmodule/xmodule/tests/test_course_module.py +++ b/common/lib/xmodule/xmodule/tests/test_course_module.py @@ -365,6 +365,16 @@ class SelfPacedTestCase(unittest.TestCase): self.assertFalse(self.course.self_paced) +class BypassHomeTestCase(unittest.TestCase): + """Tests for setting which allows course home to be bypassed.""" + def setUp(self): + super(BypassHomeTestCase, self).setUp() + self.course = get_dummy_course('2012-12-02T12:00') + + def test_default(self): + self.assertFalse(self.course.bypass_home) + + class CourseDescriptorTestCase(unittest.TestCase): """ Tests for a select few functions from CourseDescriptor. diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index cae1b67c95..f8221cc247 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -652,6 +652,44 @@ class ViewsTestCase(ModuleStoreTestCase): response = self.client.get(url) self.assertRedirects(response, reverse('signin_user') + '?next=' + url) + def test_bypass_course_info(self): + course_id = unicode(self.course_key) + request = self.request_factory.get( + reverse('info', args=[course_id]) + ) + + # Middleware is not supported by the request factory. Simulate a + # logged-in user by setting request.user manually. + request.user = self.user + mako_middleware_process_request(request) + + self.assertFalse(self.course.bypass_home) + + self.assertIsNone(request.META.get('HTTP_REFERER')) # pylint: disable=no-member + response = views.course_info(request, course_id) + self.assertEqual(response.status_code, 200) + + request.META['HTTP_REFERER'] = reverse('dashboard') # pylint: disable=no-member + response = views.course_info(request, course_id) + self.assertEqual(response.status_code, 200) + + self.course.bypass_home = True + self.store.update_item(self.course, self.user.id) # pylint: disable=no-member + self.assertTrue(self.course.bypass_home) + + response = views.course_info(request, course_id) + + # assertRedirects would be great here, but it forces redirections to be absolute URLs. + self.assertEqual(response.status_code, 302) + self.assertEqual( + response.url, + reverse('courseware', args=[course_id]) + ) + + request.META['HTTP_REFERER'] = 'foo' # pylint: disable=no-member + response = views.course_info(request, course_id) + self.assertEqual(response.status_code, 200) + @attr('shard_1') # setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index ee6df9b384..6eb261f7a3 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -694,6 +694,10 @@ def course_info(request, course_id): if request.user.is_authenticated() and survey.utils.must_answer_survey(course, user): return redirect(reverse('course_survey', args=[unicode(course.id)])) + is_from_dashboard = reverse('dashboard') in request.META.get('HTTP_REFERER', []) + if course.bypass_home and is_from_dashboard: + return redirect(reverse('courseware', args=[course_id])) + studio_url = get_studio_url(course, 'course_info') # link to where the student should go to enroll in the course: From 003cbd5a1d15038e0f573ddaadb2e4d639585c90 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Mon, 4 Apr 2016 12:09:30 -0400 Subject: [PATCH 4/5] Fix discussion category map for self-paced courses Prevents start date alone from being used to filter out categories in self-paced courses during construction of the category map. ECOM-4017. --- .../django_comment_client/tests/test_utils.py | 98 ++++++++++++++++++- lms/djangoapps/django_comment_client/utils.py | 8 +- lms/envs/aws.py | 1 - 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index 024e459576..677144e8da 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -666,6 +666,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): self.create_discussion("Chapter 2 / Section 1 / Subsection 2", "Discussion", start=later) self.create_discussion("Chapter 3 / Section 1", "Discussion", start=later) + self.assertFalse(self.course.self_paced) self.assert_category_map_equals( { "entries": {}, @@ -696,7 +697,102 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): "children": ["Chapter 1", "Chapter 2"] } ) - self.maxDiff = None + + def test_self_paced_start_date_filter(self): + self.course.self_paced = True + self.course.save() + + now = datetime.datetime.now() + later = datetime.datetime.max + self.create_discussion("Chapter 1", "Discussion 1", start=now) + self.create_discussion("Chapter 1", "Discussion 2", start=later) + self.create_discussion("Chapter 2", "Discussion", start=now) + self.create_discussion("Chapter 2 / Section 1 / Subsection 1", "Discussion", start=later) + self.create_discussion("Chapter 2 / Section 1 / Subsection 2", "Discussion", start=later) + self.create_discussion("Chapter 3 / Section 1", "Discussion", start=later) + + self.assertTrue(self.course.self_paced) + self.assert_category_map_equals( + { + "entries": {}, + "subcategories": { + "Chapter 1": { + "entries": { + "Discussion 1": { + "id": "discussion1", + "sort_key": None, + "is_cohorted": False, + }, + "Discussion 2": { + "id": "discussion2", + "sort_key": None, + "is_cohorted": False, + } + }, + "subcategories": {}, + "children": ["Discussion 1", "Discussion 2"] + }, + "Chapter 2": { + "entries": { + "Discussion": { + "id": "discussion3", + "sort_key": None, + "is_cohorted": False, + } + }, + "subcategories": { + "Section 1": { + "entries": {}, + "subcategories": { + "Subsection 1": { + "entries": { + "Discussion": { + "id": "discussion4", + "sort_key": None, + "is_cohorted": False, + } + }, + "subcategories": {}, + "children": ["Discussion"] + }, + "Subsection 2": { + "entries": { + "Discussion": { + "id": "discussion5", + "sort_key": None, + "is_cohorted": False, + } + }, + "subcategories": {}, + "children": ["Discussion"] + } + }, + "children": ["Subsection 1", "Subsection 2"] + } + }, + "children": ["Discussion", "Section 1"] + }, + "Chapter 3": { + "entries": {}, + "subcategories": { + "Section 1": { + "entries": { + "Discussion": { + "id": "discussion6", + "sort_key": None, + "is_cohorted": False, + } + }, + "subcategories": {}, + "children": ["Discussion"] + } + }, + "children": ["Section 1"] + } + }, + "children": ["Chapter 1", "Chapter 2", "Chapter 3"] + } + ) def test_sort_inline_explicit(self): self.create_discussion("Chapter", "Discussion 1", sort_key="D") diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index d079b5be41..21730c0bcb 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -198,7 +198,7 @@ def get_discussion_id_map(course, user): return dict(map(get_discussion_id_map_entry, get_accessible_discussion_modules(course, user))) -def _filter_unstarted_categories(category_map): +def _filter_unstarted_categories(category_map, course): """ Returns a subset of categories from the provided map which have not yet met the start date Includes information about category children, subcategories (different), and entries @@ -221,7 +221,7 @@ def _filter_unstarted_categories(category_map): for child in unfiltered_map["children"]: if child in unfiltered_map["entries"]: - if unfiltered_map["entries"][child]["start_date"] <= now: + if course.self_paced or unfiltered_map["entries"][child]["start_date"] <= now: filtered_map["children"].append(child) filtered_map["entries"][child] = {} for key in unfiltered_map["entries"][child]: @@ -230,7 +230,7 @@ def _filter_unstarted_categories(category_map): else: log.debug(u"Filtering out:%s with start_date: %s", child, unfiltered_map["entries"][child]["start_date"]) else: - if unfiltered_map["subcategories"][child]["start_date"] < now: + if course.self_paced or unfiltered_map["subcategories"][child]["start_date"] < now: filtered_map["children"].append(child) filtered_map["subcategories"][child] = {} unfiltered_queue.append(unfiltered_map["subcategories"][child]) @@ -382,7 +382,7 @@ def get_discussion_category_map(course, user, cohorted_if_in_list=False, exclude _sort_map_entries(category_map, course.discussion_sort_alpha) - return _filter_unstarted_categories(category_map) if exclude_unstarted else category_map + return _filter_unstarted_categories(category_map, course) if exclude_unstarted else category_map def discussion_category_id_access(course, user, discussion_id): diff --git a/lms/envs/aws.py b/lms/envs/aws.py index f3fa7a5d3d..8692dd42a3 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -190,7 +190,6 @@ if ENV_TOKENS.get('SESSION_COOKIE_NAME', None): SESSION_COOKIE_NAME = str(ENV_TOKENS.get('SESSION_COOKIE_NAME')) BOOK_URL = ENV_TOKENS['BOOK_URL'] -MEDIA_URL = ENV_TOKENS['MEDIA_URL'] LOG_DIR = ENV_TOKENS['LOG_DIR'] CACHES = ENV_TOKENS['CACHES'] From cc7aa349a1968b89d916741a5727a7af1a91f6fe Mon Sep 17 00:00:00 2001 From: muzaffaryousaf Date: Fri, 1 Apr 2016 19:42:12 +0500 Subject: [PATCH 5/5] Removing check for edx notes for OAUTH token timeout. TNL-4336 --- lms/envs/aws.py | 1 + lms/envs/common.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 8692dd42a3..8995a09385 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -618,6 +618,7 @@ if FEATURES.get('ENABLE_OAUTH2_PROVIDER'): OAUTH_EXPIRE_DELTA_PUBLIC = datetime.timedelta( days=ENV_TOKENS.get('OAUTH_EXPIRE_PUBLIC_CLIENT_DAYS', OAUTH_EXPIRE_PUBLIC_CLIENT_DAYS) ) + OAUTH_ID_TOKEN_EXPIRATION = ENV_TOKENS.get('OAUTH_ID_TOKEN_EXPIRATION', OAUTH_ID_TOKEN_EXPIRATION) ##### ADVANCED_SECURITY_CONFIG ##### diff --git a/lms/envs/common.py b/lms/envs/common.py index 9b1c85f206..4876b7dbf2 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2551,9 +2551,8 @@ COURSE_ABOUT_VISIBILITY_PERMISSION = 'see_exists' # Enrollment API Cache Timeout ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = 60 -# for Student Notes we would like to avoid too frequent token refreshes (default is 30 seconds) -if FEATURES['ENABLE_EDXNOTES']: - OAUTH_ID_TOKEN_EXPIRATION = 60 * 60 + +OAUTH_ID_TOKEN_EXPIRATION = 60 * 60 # These tabs are currently disabled NOTES_DISABLED_TABS = ['course_structure', 'tags']