Commit 0fd06e80 by Renzo Lucioni

Merge pull request #12040 from edx/patch/2016-04-05

Patch/2016 04 05
parents 69305ce8 cc7aa349
...@@ -50,7 +50,9 @@ class CourseMetadata(object): ...@@ -50,7 +50,9 @@ class CourseMetadata(object):
'is_time_limited', 'is_time_limited',
'is_practice_exam', 'is_practice_exam',
'exam_review_rules', 'exam_review_rules',
'self_paced' 'self_paced',
'chrome',
'default_tab',
] ]
@classmethod @classmethod
......
...@@ -752,6 +752,17 @@ class CourseFields(object): ...@@ -752,6 +752,17 @@ class CourseFields(object):
scope=Scope.settings 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( enable_subsection_gating = Boolean(
display_name=_("Enable Subsection Prerequisites"), display_name=_("Enable Subsection Prerequisites"),
help=_( help=_(
......
...@@ -365,6 +365,16 @@ class SelfPacedTestCase(unittest.TestCase): ...@@ -365,6 +365,16 @@ class SelfPacedTestCase(unittest.TestCase):
self.assertFalse(self.course.self_paced) 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): class CourseDescriptorTestCase(unittest.TestCase):
""" """
Tests for a select few functions from CourseDescriptor. Tests for a select few functions from CourseDescriptor.
......
...@@ -184,9 +184,7 @@ class AdvancedSettingsPage(CoursePage): ...@@ -184,9 +184,7 @@ class AdvancedSettingsPage(CoursePage):
'display_coursenumber', 'display_coursenumber',
'display_organization', 'display_organization',
'catalog_visibility', 'catalog_visibility',
'chrome',
'days_early_for_beta', 'days_early_for_beta',
'default_tab',
'disable_progress_graph', 'disable_progress_graph',
'discussion_blackouts', 'discussion_blackouts',
'discussion_sort_alpha', 'discussion_sort_alpha',
......
...@@ -182,7 +182,11 @@ class CourseEndDate(DateSummary): ...@@ -182,7 +182,11 @@ class CourseEndDate(DateSummary):
@property @property
def description(self): def description(self):
if datetime.now(pytz.UTC) <= self.date: 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.') return _('This course is archived, which means you can review course content but it is no longer active.')
@property @property
......
...@@ -173,7 +173,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): ...@@ -173,7 +173,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
## CourseEndDate ## 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) self.setup_course_and_user(days_till_start=-1)
block = CourseEndDate(self.course, self.user) block = CourseEndDate(self.course, self.user)
self.assertEqual( self.assertEqual(
...@@ -181,6 +181,14 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): ...@@ -181,6 +181,14 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'To earn a certificate, you must complete all requirements before this date.' '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): def test_course_end_date_after_course(self):
self.setup_course_and_user(days_till_start=-2, days_till_end=-1) self.setup_course_and_user(days_till_start=-2, days_till_end=-1)
block = CourseEndDate(self.course, self.user) block = CourseEndDate(self.course, self.user)
......
...@@ -652,6 +652,44 @@ class ViewsTestCase(ModuleStoreTestCase): ...@@ -652,6 +652,44 @@ class ViewsTestCase(ModuleStoreTestCase):
response = self.client.get(url) response = self.client.get(url)
self.assertRedirects(response, reverse('signin_user') + '?next=' + 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') @attr('shard_1')
# setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly # setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
......
...@@ -694,6 +694,10 @@ def course_info(request, course_id): ...@@ -694,6 +694,10 @@ def course_info(request, course_id):
if request.user.is_authenticated() and survey.utils.must_answer_survey(course, user): if request.user.is_authenticated() and survey.utils.must_answer_survey(course, user):
return redirect(reverse('course_survey', args=[unicode(course.id)])) 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') studio_url = get_studio_url(course, 'course_info')
# link to where the student should go to enroll in the course: # link to where the student should go to enroll in the course:
......
...@@ -666,6 +666,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): ...@@ -666,6 +666,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
self.create_discussion("Chapter 2 / Section 1 / Subsection 2", "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.create_discussion("Chapter 3 / Section 1", "Discussion", start=later)
self.assertFalse(self.course.self_paced)
self.assert_category_map_equals( self.assert_category_map_equals(
{ {
"entries": {}, "entries": {},
...@@ -696,7 +697,102 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): ...@@ -696,7 +697,102 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
"children": ["Chapter 1", "Chapter 2"] "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): def test_sort_inline_explicit(self):
self.create_discussion("Chapter", "Discussion 1", sort_key="D") self.create_discussion("Chapter", "Discussion 1", sort_key="D")
......
...@@ -198,7 +198,7 @@ def get_discussion_id_map(course, user): ...@@ -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))) 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 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 Includes information about category children, subcategories (different), and entries
...@@ -221,7 +221,7 @@ def _filter_unstarted_categories(category_map): ...@@ -221,7 +221,7 @@ def _filter_unstarted_categories(category_map):
for child in unfiltered_map["children"]: for child in unfiltered_map["children"]:
if child in unfiltered_map["entries"]: 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["children"].append(child)
filtered_map["entries"][child] = {} filtered_map["entries"][child] = {}
for key in unfiltered_map["entries"][child]: for key in unfiltered_map["entries"][child]:
...@@ -230,7 +230,7 @@ def _filter_unstarted_categories(category_map): ...@@ -230,7 +230,7 @@ def _filter_unstarted_categories(category_map):
else: else:
log.debug(u"Filtering out:%s with start_date: %s", child, unfiltered_map["entries"][child]["start_date"]) log.debug(u"Filtering out:%s with start_date: %s", child, unfiltered_map["entries"][child]["start_date"])
else: 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["children"].append(child)
filtered_map["subcategories"][child] = {} filtered_map["subcategories"][child] = {}
unfiltered_queue.append(unfiltered_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 ...@@ -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) _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): def discussion_category_id_access(course, user, discussion_id):
......
...@@ -190,7 +190,6 @@ if ENV_TOKENS.get('SESSION_COOKIE_NAME', None): ...@@ -190,7 +190,6 @@ if ENV_TOKENS.get('SESSION_COOKIE_NAME', None):
SESSION_COOKIE_NAME = str(ENV_TOKENS.get('SESSION_COOKIE_NAME')) SESSION_COOKIE_NAME = str(ENV_TOKENS.get('SESSION_COOKIE_NAME'))
BOOK_URL = ENV_TOKENS['BOOK_URL'] BOOK_URL = ENV_TOKENS['BOOK_URL']
MEDIA_URL = ENV_TOKENS['MEDIA_URL']
LOG_DIR = ENV_TOKENS['LOG_DIR'] LOG_DIR = ENV_TOKENS['LOG_DIR']
CACHES = ENV_TOKENS['CACHES'] CACHES = ENV_TOKENS['CACHES']
...@@ -619,6 +618,7 @@ if FEATURES.get('ENABLE_OAUTH2_PROVIDER'): ...@@ -619,6 +618,7 @@ if FEATURES.get('ENABLE_OAUTH2_PROVIDER'):
OAUTH_EXPIRE_DELTA_PUBLIC = datetime.timedelta( OAUTH_EXPIRE_DELTA_PUBLIC = datetime.timedelta(
days=ENV_TOKENS.get('OAUTH_EXPIRE_PUBLIC_CLIENT_DAYS', OAUTH_EXPIRE_PUBLIC_CLIENT_DAYS) 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 ##### ##### ADVANCED_SECURITY_CONFIG #####
......
...@@ -2551,9 +2551,8 @@ COURSE_ABOUT_VISIBILITY_PERMISSION = 'see_exists' ...@@ -2551,9 +2551,8 @@ COURSE_ABOUT_VISIBILITY_PERMISSION = 'see_exists'
# Enrollment API Cache Timeout # Enrollment API Cache Timeout
ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = 60 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 # These tabs are currently disabled
NOTES_DISABLED_TABS = ['course_structure', 'tags'] NOTES_DISABLED_TABS = ['course_structure', 'tags']
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment