add expiration banner
This commit is contained in:
committed by
Matthew Piatetsky
parent
644cf1e60a
commit
6cc4fb34be
@@ -1,18 +1,21 @@
|
||||
"""
|
||||
Helpers for courseware tests.
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import json
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client, RequestFactory
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from six import text_type
|
||||
|
||||
from courseware.access import has_access
|
||||
from courseware.masquerade import handle_ajax, setup_masquerade
|
||||
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 openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
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.session = {}
|
||||
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.module_render import get_module, handle_xblock_callback
|
||||
from courseware.tests.factories import GlobalStaffFactory, StudentModuleFactory
|
||||
from courseware.tests.helpers import get_expiration_banner_text
|
||||
from courseware.testutils import RenderXBlockTestMixin
|
||||
from courseware.url_helpers import get_redirect_url
|
||||
from courseware.user_state_client import DjangoXBlockUserStateClient
|
||||
@@ -211,8 +212,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
|
||||
|
||||
@override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True)
|
||||
@ddt.data(
|
||||
(ModuleStoreEnum.Type.mongo, 10, 157),
|
||||
(ModuleStoreEnum.Type.split, 4, 153),
|
||||
(ModuleStoreEnum.Type.mongo, 10, 160),
|
||||
(ModuleStoreEnum.Type.split, 4, 156),
|
||||
)
|
||||
@ddt.unpack
|
||||
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.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)
|
||||
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.waffle_utils import WaffleSwitchNamespace
|
||||
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 (
|
||||
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._setup_masquerade_for_effective_user()
|
||||
register_course_expired_message(request, self.course)
|
||||
|
||||
return self.render(request)
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
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.util.user_messages import PageLevelMessages
|
||||
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.course_tools import CourseToolsPluginManager
|
||||
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
|
||||
# Must come after masquerading on creation of page context
|
||||
self.register_user_access_warning_messages(request, course_key)
|
||||
register_course_expired_message(request, course)
|
||||
|
||||
set_custom_metrics_for_course_key(course_key)
|
||||
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 lms.djangoapps.courseware.access_response import AccessError
|
||||
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.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)
|
||||
MAX_DURATION = timedelta(weeks=12)
|
||||
@@ -91,3 +94,21 @@ def check_course_expired(user, course):
|
||||
return AuditExpiredError(user, course, expiration_date)
|
||||
|
||||
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 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.utils import EcommerceService
|
||||
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)
|
||||
|
||||
# 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):
|
||||
url = course_home_url(self.course)
|
||||
self.client.get(url)
|
||||
@@ -414,7 +415,9 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
||||
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
|
||||
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
|
||||
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_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
|
||||
future_course = self.create_future_course()
|
||||
CourseEnrollment.enroll(user, future_course.id)
|
||||
|
||||
@@ -127,7 +127,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
|
||||
course_updates_url(self.course)
|
||||
|
||||
# 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):
|
||||
url = course_updates_url(self.course)
|
||||
self.client.get(url)
|
||||
|
||||
Reference in New Issue
Block a user