add expiration banner
This commit is contained in:
committed by
Matthew Piatetsky
parent
644cf1e60a
commit
6cc4fb34be
@@ -1,18 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
Helpers for courseware tests.
|
Helpers for courseware tests.
|
||||||
"""
|
"""
|
||||||
|
from datetime import timedelta
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import Client, RequestFactory
|
from django.test.client import Client, RequestFactory
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.timezone import now
|
||||||
from six import text_type
|
from six import text_type
|
||||||
|
|
||||||
from courseware.access import has_access
|
from courseware.access import has_access
|
||||||
from courseware.masquerade import handle_ajax, setup_masquerade
|
from courseware.masquerade import handle_ajax, setup_masquerade
|
||||||
from edxmako.shortcuts import render_to_string
|
from edxmako.shortcuts import render_to_string
|
||||||
|
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
|
||||||
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
|
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
|
||||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||||
from openedx.core.lib.url_utils import quote_slashes
|
from openedx.core.lib.url_utils import quote_slashes
|
||||||
@@ -348,3 +351,18 @@ def _create_mock_json_request(user, data, method='POST'):
|
|||||||
request.user = user
|
request.user = user
|
||||||
request.session = {}
|
request.session = {}
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
def get_expiration_banner_text(user, course):
|
||||||
|
"""
|
||||||
|
Get text for banner that messages user course expiration date
|
||||||
|
for different tests that depend on it.
|
||||||
|
"""
|
||||||
|
expiration_date = (now() + timedelta(weeks=4)).strftime('%b %-d')
|
||||||
|
upgrade_link = verified_upgrade_deadline_link(user=user, course=course)
|
||||||
|
bannerText = 'Your access to this course expires on {expiration_date}. \
|
||||||
|
<a href="{upgrade_link}">Upgrade now</a> for unlimited access.'.format(
|
||||||
|
expiration_date=expiration_date,
|
||||||
|
upgrade_link=upgrade_link
|
||||||
|
)
|
||||||
|
return bannerText
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ from courseware.access_utils import check_course_open_for_learner
|
|||||||
from courseware.model_data import FieldDataCache, set_score
|
from courseware.model_data import FieldDataCache, set_score
|
||||||
from courseware.module_render import get_module, handle_xblock_callback
|
from courseware.module_render import get_module, handle_xblock_callback
|
||||||
from courseware.tests.factories import GlobalStaffFactory, StudentModuleFactory
|
from courseware.tests.factories import GlobalStaffFactory, StudentModuleFactory
|
||||||
|
from courseware.tests.helpers import get_expiration_banner_text
|
||||||
from courseware.testutils import RenderXBlockTestMixin
|
from courseware.testutils import RenderXBlockTestMixin
|
||||||
from courseware.url_helpers import get_redirect_url
|
from courseware.url_helpers import get_redirect_url
|
||||||
from courseware.user_state_client import DjangoXBlockUserStateClient
|
from courseware.user_state_client import DjangoXBlockUserStateClient
|
||||||
@@ -211,8 +212,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
|
|||||||
|
|
||||||
@override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True)
|
@override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True)
|
||||||
@ddt.data(
|
@ddt.data(
|
||||||
(ModuleStoreEnum.Type.mongo, 10, 157),
|
(ModuleStoreEnum.Type.mongo, 10, 160),
|
||||||
(ModuleStoreEnum.Type.split, 4, 153),
|
(ModuleStoreEnum.Type.split, 4, 156),
|
||||||
)
|
)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
|
def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
|
||||||
@@ -2645,11 +2646,71 @@ class TestIndexViewWithGating(ModuleStoreTestCase, MilestonesTestCaseMixin):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEquals(response.status_code, 200)
|
self.assertEquals(response.status_code, 200)
|
||||||
self.assertIn("Content Locked", response.content)
|
self.assertIn("Content Locked", response.content)
|
||||||
|
|
||||||
|
|
||||||
|
@attr(shard=5)
|
||||||
|
class TestIndexViewWithCourseDurationLimits(ModuleStoreTestCase):
|
||||||
|
"""
|
||||||
|
Test the index view for a course with course duration limits enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Set up the initial test data.
|
||||||
|
"""
|
||||||
|
super(TestIndexViewWithCourseDurationLimits, self).setUp()
|
||||||
|
|
||||||
|
self.user = UserFactory()
|
||||||
|
self.course = CourseFactory.create(start=datetime.now() - timedelta(weeks=1))
|
||||||
|
with self.store.bulk_operations(self.course.id):
|
||||||
|
self.chapter = ItemFactory.create(parent=self.course, category="chapter")
|
||||||
|
self.sequential = ItemFactory.create(parent=self.chapter, category='sequential')
|
||||||
|
|
||||||
|
CourseEnrollmentFactory(user=self.user, course_id=self.course.id)
|
||||||
|
|
||||||
|
@override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True)
|
||||||
|
def test_index_with_course_duration_limits(self):
|
||||||
|
"""
|
||||||
|
Test that the courseware contains the course expiration banner
|
||||||
|
when course_duration_limits are enabled.
|
||||||
|
"""
|
||||||
|
self.assertTrue(self.client.login(username=self.user.username, password='test'))
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
'courseware_section',
|
||||||
|
kwargs={
|
||||||
|
'course_id': unicode(self.course.id),
|
||||||
|
'chapter': self.chapter.url_name,
|
||||||
|
'section': self.sequential.url_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
bannerText = get_expiration_banner_text(self.user, self.course)
|
||||||
|
self.assertContains(response, bannerText, html=True)
|
||||||
|
|
||||||
|
@override_waffle_flag(CONTENT_TYPE_GATING_FLAG, False)
|
||||||
|
def test_index_without_course_duration_limits(self):
|
||||||
|
"""
|
||||||
|
Test that the courseware does not contain the course expiration banner
|
||||||
|
when course_duration_limits are disabled.
|
||||||
|
"""
|
||||||
|
self.assertTrue(self.client.login(username=self.user.username, password='test'))
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
'courseware_section',
|
||||||
|
kwargs={
|
||||||
|
'course_id': unicode(self.course.id),
|
||||||
|
'chapter': self.chapter.url_name,
|
||||||
|
'section': self.sequential.url_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
bannerText = get_expiration_banner_text(self.user, self.course)
|
||||||
|
self.assertNotContains(response, bannerText, html=True)
|
||||||
|
|
||||||
|
|
||||||
@attr(shard=5)
|
@attr(shard=5)
|
||||||
class TestRenderXBlock(RenderXBlockTestMixin, ModuleStoreTestCase, CompletionWaffleTestMixin):
|
class TestRenderXBlock(RenderXBlockTestMixin, ModuleStoreTestCase, CompletionWaffleTestMixin):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
|||||||
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
||||||
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
|
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
|
||||||
from openedx.core.djangolib.markup import HTML, Text
|
from openedx.core.djangolib.markup import HTML, Text
|
||||||
|
from openedx.features.course_duration_limits.access import register_course_expired_message
|
||||||
from openedx.features.course_experience import (
|
from openedx.features.course_experience import (
|
||||||
COURSE_OUTLINE_PAGE_FLAG, default_course_url_name, COURSE_ENABLE_UNENROLLED_ACCESS_FLAG
|
COURSE_OUTLINE_PAGE_FLAG, default_course_url_name, COURSE_ENABLE_UNENROLLED_ACCESS_FLAG
|
||||||
)
|
)
|
||||||
@@ -132,6 +133,8 @@ class CoursewareIndex(View):
|
|||||||
|
|
||||||
self.is_staff = has_access(request.user, 'staff', self.course)
|
self.is_staff = has_access(request.user, 'staff', self.course)
|
||||||
self._setup_masquerade_for_effective_user()
|
self._setup_masquerade_for_effective_user()
|
||||||
|
register_course_expired_message(request, self.course)
|
||||||
|
|
||||||
return self.render(request)
|
return self.render(request)
|
||||||
except Exception as exception: # pylint: disable=broad-except
|
except Exception as exception: # pylint: disable=broad-except
|
||||||
return CourseTabView.handle_exceptions(request, self.course, exception)
|
return CourseTabView.handle_exceptions(request, self.course, exception)
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
|||||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||||
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
||||||
from openedx.core.djangolib.markup import HTML, Text
|
from openedx.core.djangolib.markup import HTML, Text
|
||||||
|
from openedx.features.course_duration_limits.access import register_course_expired_message
|
||||||
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, course_home_url_name
|
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, course_home_url_name
|
||||||
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
|
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
|
||||||
from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView
|
from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView
|
||||||
@@ -504,6 +505,7 @@ class CourseTabView(EdxFragmentView):
|
|||||||
# Show warnings if the user has limited access
|
# Show warnings if the user has limited access
|
||||||
# Must come after masquerading on creation of page context
|
# Must come after masquerading on creation of page context
|
||||||
self.register_user_access_warning_messages(request, course_key)
|
self.register_user_access_warning_messages(request, course_key)
|
||||||
|
register_course_expired_message(request, course)
|
||||||
|
|
||||||
set_custom_metrics_for_course_key(course_key)
|
set_custom_metrics_for_course_key(course_key)
|
||||||
return super(CourseTabView, self).get(request, course=course, page_context=page_context, **kwargs)
|
return super(CourseTabView, self).get(request, course=course, page_context=page_context, **kwargs)
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ from django.utils.translation import ugettext as _
|
|||||||
from util.date_utils import DEFAULT_SHORT_DATE_FORMAT, strftime_localized
|
from util.date_utils import DEFAULT_SHORT_DATE_FORMAT, strftime_localized
|
||||||
from lms.djangoapps.courseware.access_response import AccessError
|
from lms.djangoapps.courseware.access_response import AccessError
|
||||||
from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED
|
from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED
|
||||||
|
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
|
||||||
from openedx.core.djangoapps.catalog.utils import get_course_run_details
|
from openedx.core.djangoapps.catalog.utils import get_course_run_details
|
||||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||||
|
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
||||||
|
from openedx.core.djangolib.markup import HTML
|
||||||
|
from openedx.features.course_duration_limits.config import CONTENT_TYPE_GATING_FLAG
|
||||||
|
|
||||||
MIN_DURATION = timedelta(weeks=4)
|
MIN_DURATION = timedelta(weeks=4)
|
||||||
MAX_DURATION = timedelta(weeks=12)
|
MAX_DURATION = timedelta(weeks=12)
|
||||||
@@ -91,3 +94,21 @@ def check_course_expired(user, course):
|
|||||||
return AuditExpiredError(user, course, expiration_date)
|
return AuditExpiredError(user, course, expiration_date)
|
||||||
|
|
||||||
return ACCESS_GRANTED
|
return ACCESS_GRANTED
|
||||||
|
|
||||||
|
|
||||||
|
def register_course_expired_message(request, course):
|
||||||
|
"""
|
||||||
|
Add a banner notifying the user of the user course expiration date if it exists.
|
||||||
|
"""
|
||||||
|
if CONTENT_TYPE_GATING_FLAG.is_enabled():
|
||||||
|
expiration_date = get_user_course_expiration_date(request.user, course)
|
||||||
|
if expiration_date:
|
||||||
|
upgrade_message = _('Your access to this course expires on {expiration_date}. \
|
||||||
|
<a href="{upgrade_link}">Upgrade now</a> for unlimited access.')
|
||||||
|
PageLevelMessages.register_info_message(
|
||||||
|
request,
|
||||||
|
HTML(upgrade_message).format(
|
||||||
|
expiration_date=expiration_date.strftime('%b %-d'),
|
||||||
|
upgrade_link=verified_upgrade_deadline_link(user=request.user, course=course)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from waffle.testutils import override_flag
|
|||||||
|
|
||||||
from course_modes.models import CourseMode
|
from course_modes.models import CourseMode
|
||||||
from courseware.tests.factories import StaffFactory
|
from courseware.tests.factories import StaffFactory
|
||||||
|
from courseware.tests.helpers import get_expiration_banner_text
|
||||||
from lms.djangoapps.commerce.models import CommerceConfiguration
|
from lms.djangoapps.commerce.models import CommerceConfiguration
|
||||||
from lms.djangoapps.commerce.utils import EcommerceService
|
from lms.djangoapps.commerce.utils import EcommerceService
|
||||||
from lms.djangoapps.course_goals.api import add_course_goal, remove_course_goal
|
from lms.djangoapps.course_goals.api import add_course_goal, remove_course_goal
|
||||||
@@ -180,7 +181,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
|
|||||||
course_home_url(self.course)
|
course_home_url(self.course)
|
||||||
|
|
||||||
# Fetch the view and verify the query counts
|
# Fetch the view and verify the query counts
|
||||||
with self.assertNumQueries(67, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
|
with self.assertNumQueries(70, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
|
||||||
with check_mongo_calls(4):
|
with check_mongo_calls(4):
|
||||||
url = course_home_url(self.course)
|
url = course_home_url(self.course)
|
||||||
self.client.get(url)
|
self.client.get(url)
|
||||||
@@ -414,7 +415,9 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
|||||||
2) Unenrolled users are shown a course message allowing them to enroll
|
2) Unenrolled users are shown a course message allowing them to enroll
|
||||||
3) Enrolled users who show up on the course page after the course has begun
|
3) Enrolled users who show up on the course page after the course has begun
|
||||||
are not shown a course message.
|
are not shown a course message.
|
||||||
4) Enrolled users who show up on the course page before the course begins
|
4) Enrolled users who show up on the course page after the course has begun will
|
||||||
|
see the course expiration banner if course duration limits are on for the course.
|
||||||
|
5) Enrolled users who show up on the course page before the course begins
|
||||||
are shown a message explaining when the course starts as well as a call to
|
are shown a message explaining when the course starts as well as a call to
|
||||||
action button that allows them to add a calendar event.
|
action button that allows them to add a calendar event.
|
||||||
"""
|
"""
|
||||||
@@ -439,6 +442,20 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
|||||||
self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED)
|
self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED)
|
||||||
self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
|
self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
|
||||||
|
|
||||||
|
# Verify that enrolled users are shown the course expiration banner if content gating is enabled
|
||||||
|
with override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True):
|
||||||
|
url = course_home_url(self.course)
|
||||||
|
response = self.client.get(url)
|
||||||
|
bannerText = get_expiration_banner_text(user, self.course)
|
||||||
|
self.assertContains(response, bannerText, html=True)
|
||||||
|
|
||||||
|
# Verify that enrolled users are not shown the course expiration banner if content gating is disabled
|
||||||
|
with override_waffle_flag(CONTENT_TYPE_GATING_FLAG, False):
|
||||||
|
url = course_home_url(self.course)
|
||||||
|
response = self.client.get(url)
|
||||||
|
bannerText = get_expiration_banner_text(user, self.course)
|
||||||
|
self.assertNotContains(response, bannerText, html=True)
|
||||||
|
|
||||||
# Verify that enrolled users are shown 'days until start' message before start date
|
# Verify that enrolled users are shown 'days until start' message before start date
|
||||||
future_course = self.create_future_course()
|
future_course = self.create_future_course()
|
||||||
CourseEnrollment.enroll(user, future_course.id)
|
CourseEnrollment.enroll(user, future_course.id)
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
|
|||||||
course_updates_url(self.course)
|
course_updates_url(self.course)
|
||||||
|
|
||||||
# Fetch the view and verify that the query counts haven't changed
|
# Fetch the view and verify that the query counts haven't changed
|
||||||
with self.assertNumQueries(39, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
|
with self.assertNumQueries(42, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
|
||||||
with check_mongo_calls(4):
|
with check_mongo_calls(4):
|
||||||
url = course_updates_url(self.course)
|
url = course_updates_url(self.course)
|
||||||
self.client.get(url)
|
self.client.get(url)
|
||||||
|
|||||||
Reference in New Issue
Block a user