Display warning message on course outline in Studio when course contains deprecated features/components
TNL-2303
This commit is contained in:
@@ -27,6 +27,7 @@ from xmodule.tabs import CourseTab, CourseTabList, InvalidTabsException
|
||||
from openedx.core.lib.course_tabs import CourseTabPluginManager
|
||||
from openedx.core.djangoapps.credit.api import is_credit_course, get_credit_requirements
|
||||
from openedx.core.djangoapps.credit.tasks import update_credit_course_requirements
|
||||
from openedx.core.djangoapps.content.course_structures.api.v0 import api, errors
|
||||
from xmodule.modulestore import EdxJSONEncoder
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError
|
||||
from opaque_keys import InvalidKeyError
|
||||
@@ -48,6 +49,7 @@ from contentstore.utils import (
|
||||
get_lms_link_for_item,
|
||||
reverse_course_url,
|
||||
reverse_library_url,
|
||||
reverse_usage_url,
|
||||
reverse_url,
|
||||
remove_all_instructors,
|
||||
)
|
||||
@@ -473,6 +475,44 @@ def _get_rerun_link_for_item(course_key):
|
||||
return reverse_course_url('course_rerun_handler', course_key)
|
||||
|
||||
|
||||
def _deprecated_blocks_info(course_module, deprecated_block_types):
|
||||
"""
|
||||
Returns deprecation information about `deprecated_block_types`
|
||||
|
||||
Arguments:
|
||||
course_module (CourseDescriptor): course object
|
||||
deprecated_block_types (list): list of deprecated blocks types
|
||||
|
||||
Returns:
|
||||
Dict with following keys:
|
||||
block_types (list): list containing types of all deprecated blocks
|
||||
block_types_enabled (bool): True if any or all `deprecated_blocks` present in Advanced Module List else False
|
||||
blocks (list): List of `deprecated_block_types` component names and their parent's url
|
||||
advance_settings_url (str): URL to advance settings page
|
||||
"""
|
||||
data = {
|
||||
'block_types': deprecated_block_types,
|
||||
'block_types_enabled': any(
|
||||
block_type in course_module.advanced_modules for block_type in deprecated_block_types
|
||||
),
|
||||
'blocks': [],
|
||||
'advance_settings_url': reverse_course_url('advanced_settings_handler', course_module.id)
|
||||
}
|
||||
|
||||
try:
|
||||
structure_data = api.course_structure(course_module.id, block_types=deprecated_block_types)
|
||||
except errors.CourseStructureNotAvailableError:
|
||||
return data
|
||||
|
||||
blocks = []
|
||||
for block in structure_data['blocks'].values():
|
||||
blocks.append([reverse_usage_url('container_handler', block['parent']), block['display_name']])
|
||||
|
||||
data['blocks'].extend(blocks)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def course_index(request, course_key):
|
||||
@@ -500,6 +540,8 @@ def course_index(request, course_key):
|
||||
except (ItemNotFoundError, CourseActionStateItemNotFoundError):
|
||||
current_action = None
|
||||
|
||||
deprecated_blocks_info = _deprecated_blocks_info(course_module, settings.DEPRECATED_BLOCK_TYPES)
|
||||
|
||||
return render_to_response('course_outline.html', {
|
||||
'context_course': course_module,
|
||||
'lms_link': lms_link,
|
||||
@@ -513,6 +555,7 @@ def course_index(request, course_key):
|
||||
'course_release_date': course_release_date,
|
||||
'settings_url': settings_url,
|
||||
'reindex_link': reindex_link,
|
||||
'deprecated_blocks_info': deprecated_blocks_info,
|
||||
'notification_dismiss_url': reverse_course_url(
|
||||
'course_notifications_handler',
|
||||
current_action.course_key,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
Unit tests for getting the list of courses and the course outline.
|
||||
"""
|
||||
import ddt
|
||||
import json
|
||||
import lxml
|
||||
import datetime
|
||||
import os
|
||||
import mock
|
||||
import pytz
|
||||
|
||||
@@ -14,8 +14,10 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
from contentstore.courseware_index import CoursewareSearchIndexer, SearchIndexingError
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from contentstore.utils import reverse_course_url, reverse_library_url, add_instructor
|
||||
from contentstore.views.course import course_outline_initial_state, reindex_course_and_check_access
|
||||
from contentstore.utils import reverse_course_url, reverse_library_url, add_instructor, reverse_usage_url
|
||||
from contentstore.views.course import (
|
||||
course_outline_initial_state, reindex_course_and_check_access, _deprecated_blocks_info
|
||||
)
|
||||
from contentstore.views.item import create_xblock_info, VisibilityState
|
||||
from course_action_state.managers import CourseRerunUIStateManager
|
||||
from course_action_state.models import CourseRerunState
|
||||
@@ -225,6 +227,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
self.assert_correct_json_response(child_response)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestCourseOutline(CourseTestCase):
|
||||
"""
|
||||
Unit tests for the course outline.
|
||||
@@ -340,6 +343,146 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assertEqual(_get_release_date(response), get_default_time_display(self.course.start))
|
||||
_assert_settings_link_present(response)
|
||||
|
||||
def _create_test_data(self, course_module, create_blocks=False, publish=True, block_types=None):
|
||||
"""
|
||||
Create data for test.
|
||||
"""
|
||||
if create_blocks:
|
||||
for block_type in block_types:
|
||||
ItemFactory.create(
|
||||
parent_location=self.vertical.location,
|
||||
category=block_type,
|
||||
display_name='{} Problem'.format(block_type)
|
||||
)
|
||||
|
||||
if not publish:
|
||||
self.store.unpublish(self.vertical.location, self.user.id)
|
||||
|
||||
course_module.advanced_modules.extend(block_types)
|
||||
|
||||
def _verify_deprecated_info(self, course_id, advanced_modules, info, deprecated_block_types):
|
||||
"""
|
||||
Verify deprecated info.
|
||||
"""
|
||||
expected_blocks = []
|
||||
for block_type in deprecated_block_types:
|
||||
expected_blocks.append(
|
||||
[
|
||||
reverse_usage_url('container_handler', self.vertical.location),
|
||||
'{} Problem'.format(block_type)
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(info['block_types'], deprecated_block_types)
|
||||
self.assertEqual(
|
||||
info['block_types_enabled'],
|
||||
any(component in advanced_modules for component in deprecated_block_types)
|
||||
)
|
||||
self.assertItemsEqual(info['blocks'], expected_blocks)
|
||||
self.assertEqual(
|
||||
info['advance_settings_url'],
|
||||
reverse_course_url('advanced_settings_handler', course_id)
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
{'publish': True},
|
||||
{'publish': False},
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_verify_deprecated_warning_message_with_single_feature(self, publish):
|
||||
"""
|
||||
Verify deprecated warning info for single deprecated feature.
|
||||
"""
|
||||
block_types = settings.DEPRECATED_BLOCK_TYPES
|
||||
course_module = modulestore().get_item(self.course.location)
|
||||
self._create_test_data(course_module, create_blocks=True, block_types=block_types, publish=publish)
|
||||
info = _deprecated_blocks_info(course_module, block_types)
|
||||
self._verify_deprecated_info(
|
||||
course_module.id,
|
||||
course_module.advanced_modules,
|
||||
info,
|
||||
block_types
|
||||
)
|
||||
|
||||
def test_verify_deprecated_warning_message_with_multiple_features(self):
|
||||
"""
|
||||
Verify deprecated warning info for multiple deprecated features.
|
||||
"""
|
||||
block_types = ['peergrading', 'combinedopenended', 'openassessment']
|
||||
course_module = modulestore().get_item(self.course.location)
|
||||
self._create_test_data(course_module, create_blocks=True, block_types=block_types)
|
||||
|
||||
info = _deprecated_blocks_info(course_module, block_types)
|
||||
self._verify_deprecated_info(course_module.id, course_module.advanced_modules, info, block_types)
|
||||
|
||||
@ddt.data(
|
||||
{'delete_vertical': True},
|
||||
{'delete_vertical': False},
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_deprecated_blocks_list_updated_correctly(self, delete_vertical):
|
||||
"""
|
||||
Verify that deprecated blocks list shown on banner is updated correctly.
|
||||
|
||||
Here is the scenario:
|
||||
This list of deprecated blocks shown on banner contains published
|
||||
and un-published blocks. That list should be updated when we delete
|
||||
un-published block(s). This behavior should be same if we delete
|
||||
unpublished vertical or problem.
|
||||
"""
|
||||
block_types = ['peergrading']
|
||||
course_module = modulestore().get_item(self.course.location)
|
||||
|
||||
vertical1 = ItemFactory.create(
|
||||
parent_location=self.sequential.location, category='vertical', display_name='Vert1 Subsection1'
|
||||
)
|
||||
problem1 = ItemFactory.create(
|
||||
parent_location=vertical1.location,
|
||||
category='peergrading',
|
||||
display_name='peergrading problem in vert1',
|
||||
publish_item=False
|
||||
)
|
||||
|
||||
info = _deprecated_blocks_info(course_module, block_types)
|
||||
# info['blocks'] should be empty here because there is nothing
|
||||
# published or un-published present
|
||||
self.assertEqual(info['blocks'], [])
|
||||
|
||||
vertical2 = ItemFactory.create(
|
||||
parent_location=self.sequential.location, category='vertical', display_name='Vert2 Subsection1'
|
||||
)
|
||||
ItemFactory.create(
|
||||
parent_location=vertical2.location,
|
||||
category='peergrading',
|
||||
display_name='peergrading problem in vert2',
|
||||
pubish_item=True
|
||||
)
|
||||
# At this point CourseStructure will contain both the above
|
||||
# published and un-published verticals
|
||||
|
||||
info = _deprecated_blocks_info(course_module, block_types)
|
||||
self.assertItemsEqual(
|
||||
info['blocks'],
|
||||
[
|
||||
[reverse_usage_url('container_handler', vertical1.location), 'peergrading problem in vert1'],
|
||||
[reverse_usage_url('container_handler', vertical2.location), 'peergrading problem in vert2']
|
||||
]
|
||||
)
|
||||
|
||||
# Delete the un-published vertical or problem so that CourseStructure updates its data
|
||||
if delete_vertical:
|
||||
self.store.delete_item(vertical1.location, self.user.id)
|
||||
else:
|
||||
self.store.delete_item(problem1.location, self.user.id)
|
||||
|
||||
info = _deprecated_blocks_info(course_module, block_types)
|
||||
# info['blocks'] should only contain the info about vertical2 which is published.
|
||||
# There shouldn't be any info present about un-published vertical1
|
||||
self.assertEqual(
|
||||
info['blocks'],
|
||||
[[reverse_usage_url('container_handler', vertical2.location), 'peergrading problem in vert2']]
|
||||
)
|
||||
|
||||
|
||||
class TestCourseReIndex(CourseTestCase):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user