diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 4f95444df5..e64999afb4 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -21,7 +21,7 @@ jobs: - module-name: openedx-1 path: "openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/" - module-name: openedx-2 - path: "openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/self_paced/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/ openedx/core/djangoapps/learner_pathway/" + path: "openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/ openedx/core/djangoapps/learner_pathway/" - module-name: common path: "common" - module-name: cms diff --git a/.github/workflows/unit-test-shards.json b/.github/workflows/unit-test-shards.json index 838d0fe261..d106c71e04 100644 --- a/.github/workflows/unit-test-shards.json +++ b/.github/workflows/unit-test-shards.json @@ -132,7 +132,6 @@ "openedx/core/djangoapps/programs/", "openedx/core/djangoapps/safe_sessions/", "openedx/core/djangoapps/schedules/", - "openedx/core/djangoapps/self_paced/", "openedx/core/djangoapps/service_status/", "openedx/core/djangoapps/session_inactivity_timeout/", "openedx/core/djangoapps/signals/", @@ -212,7 +211,6 @@ "openedx/core/djangoapps/programs/", "openedx/core/djangoapps/safe_sessions/", "openedx/core/djangoapps/schedules/", - "openedx/core/djangoapps/self_paced/", "openedx/core/djangoapps/service_status/", "openedx/core/djangoapps/session_inactivity_timeout/", "openedx/core/djangoapps/signals/", diff --git a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py index feb624ce49..0381d638d8 100644 --- a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py +++ b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py @@ -33,7 +33,7 @@ def print_course(course): # course.tabs looks like this -# [{u'type': u'courseware'}, {u'type': u'course_info', u'name': u'Course Info'}, {u'type': u'textbooks'}, +# [{u'type': u'courseware'}, {u'type': u'textbooks'}, # {u'type': u'discussion', u'name': u'Discussion'}, {u'type': u'wiki', u'name': u'Wiki'}, # {u'type': u'progress', u'name': u'Progress'}] @@ -53,7 +53,7 @@ command again, adding --insert or --delete to edit the list. course_help = '--course required, e.g. Stanford/CS99/2013_spring' delete_help = '--delete ' - insert_help = '--insert , e.g. 4 "course_info" "Course Info"' + insert_help = '--insert , e.g. 4 "discussion" "Discussion"' def add_arguments(self, parser): parser.add_argument('--course', diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_backfill_course_tabs.py b/cms/djangoapps/contentstore/management/commands/tests/test_backfill_course_tabs.py index 258f00a133..a83eb621b3 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_backfill_course_tabs.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_backfill_course_tabs.py @@ -40,13 +40,13 @@ class BackfillCourseTabsTest(ModuleStoreTestCase): course = CourseFactory() course.tabs = [tab for tab in course.tabs if tab.type != 'dates'] self.update_course(course, ModuleStoreEnum.UserID.test) - assert len(course.tabs) == 6 + assert len(course.tabs) == 5 assert 'dates' not in {tab.type for tab in course.tabs} call_command('backfill_course_tabs') course = self.store.get_course(course.id) - assert len(course.tabs) == 7 + assert len(course.tabs) == 6 assert 'dates' in {tab.type for tab in course.tabs} mock_logger.info.assert_any_call(f'Updating tabs for {course.id}.') mock_logger.info.assert_any_call(f'Successfully updated tabs for {course.id}.') @@ -66,16 +66,16 @@ class BackfillCourseTabsTest(ModuleStoreTestCase): CourseFactory() CourseFactory() course = CourseFactory() - course.tabs = [tab for tab in course.tabs if tab.type in ('course_info', 'courseware')] + course.tabs = [tab for tab in course.tabs if tab.type == 'courseware'] self.update_course(course, ModuleStoreEnum.UserID.test) - assert len(course.tabs) == 2 + assert len(course.tabs) == 1 assert 'dates' not in {tab.type for tab in course.tabs} assert 'progress' not in {tab.type for tab in course.tabs} call_command('backfill_course_tabs') course = self.store.get_course(course.id) - assert len(course.tabs) == 7 + assert len(course.tabs) == 6 assert 'dates' in {tab.type for tab in course.tabs} assert 'progress' in {tab.type for tab in course.tabs} mock_logger.info.assert_any_call('4 courses read from modulestore. Processing 0 to 4.') @@ -99,8 +99,8 @@ class BackfillCourseTabsTest(ModuleStoreTestCase): course_2 = CourseFactory() course_2.tabs = [tab for tab in course_2.tabs if tab.type != 'progress'] self.update_course(course_2, ModuleStoreEnum.UserID.test) - assert len(course_1.tabs) == 6 - assert len(course_2.tabs) == 6 + assert len(course_1.tabs) == 5 + assert len(course_2.tabs) == 5 assert 'dates' not in {tab.type for tab in course_1.tabs} assert 'progress' not in {tab.type for tab in course_2.tabs} @@ -108,8 +108,8 @@ class BackfillCourseTabsTest(ModuleStoreTestCase): course_1 = self.store.get_course(course_1.id) course_2 = self.store.get_course(course_2.id) - assert len(course_1.tabs) == 7 - assert len(course_2.tabs) == 7 + assert len(course_1.tabs) == 6 + assert len(course_2.tabs) == 6 assert 'dates' in {tab.type for tab in course_1.tabs} assert 'progress' in {tab.type for tab in course_2.tabs} mock_logger.info.assert_any_call('2 courses read from modulestore. Processing 0 to 2.') @@ -168,13 +168,13 @@ class BackfillCourseTabsTest(ModuleStoreTestCase): def test_arguments_batching(self, start, count, expected_tabs_modified): courses = CourseFactory.create_batch(4) for course in courses: - course.tabs = [tab for tab in course.tabs if tab.type in ('course_info', 'courseware')] + course.tabs = [tab for tab in course.tabs if tab.type == 'courseware'] course = self.update_course(course, ModuleStoreEnum.UserID.test) - assert len(course.tabs) == 2 + assert len(course.tabs) == 1 BackfillCourseTabsConfig.objects.create(enabled=True, start_index=start, count=count) call_command('backfill_course_tabs') for i, course in enumerate(courses): course = self.store.get_course(course.id) - assert len(course.tabs) == (7 if expected_tabs_modified[i] else 2), f'Wrong tabs for course index {i}' + assert len(course.tabs) == (6 if expected_tabs_modified[i] else 1), f'Wrong tabs for course index {i}' diff --git a/cms/djangoapps/contentstore/tests/test_import.py b/cms/djangoapps/contentstore/tests/test_import.py index 41b06729ac..4c63ecd735 100644 --- a/cms/djangoapps/contentstore/tests/test_import.py +++ b/cms/djangoapps/contentstore/tests/test_import.py @@ -173,7 +173,7 @@ class ContentStoreImportTest(ModuleStoreTestCase): def test_tab_name_imports_correctly(self): _module_store, _content_store, course = self.load_test_import_course() print(f"course tabs = {course.tabs}") - self.assertEqual(course.tabs[2]['name'], 'Syllabus') + self.assertEqual(course.tabs[1]['name'], 'Syllabus') def test_import_performance_mongo(self): store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo) diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py index 5482be63dc..85f6b89850 100644 --- a/cms/djangoapps/contentstore/views/tabs.py +++ b/cms/djangoapps/contentstore/views/tabs.py @@ -220,8 +220,8 @@ def get_tab_by_locator(tab_list: List[CourseTab], tab_location: Union[str, Usage def validate_args(num, tab_type): "Throws for the disallowed cases." - if num <= 1: - raise ValueError('Tabs 1 and 2 cannot be edited') + if num < 1: + raise ValueError('Tab 1 cannot be edited') if tab_type == 'static_tab': raise ValueError('Tabs of type static_tab cannot be edited here (use Studio)') diff --git a/cms/djangoapps/contentstore/views/tests/test_tabs.py b/cms/djangoapps/contentstore/views/tests/test_tabs.py index e366420d22..58a6393585 100644 --- a/cms/djangoapps/contentstore/views/tests/test_tabs.py +++ b/cms/djangoapps/contentstore/views/tests/test_tabs.py @@ -113,7 +113,7 @@ class TabsPageTests(CourseTestCase): def test_reorder_tabs_invalid_tab(self): """Test re-ordering of tabs with invalid tab""" - invalid_tab_ids = ['courseware', 'info', 'invalid_tab_id'] + invalid_tab_ids = ['courseware', 'invalid_tab_id'] # post the request resp = self.client.ajax_post( @@ -189,16 +189,14 @@ class PrimitiveTabEdit(ModuleStoreTestCase): course = CourseFactory.create() with self.assertRaises(ValueError): tabs.primitive_delete(course, 0) - with self.assertRaises(ValueError): - tabs.primitive_delete(course, 1) with self.assertRaises(IndexError): - tabs.primitive_delete(course, 7) + tabs.primitive_delete(course, 6) - assert course.tabs[2] != {'type': 'dates', 'name': 'Dates'} - tabs.primitive_delete(course, 2) + assert course.tabs[1] != {'type': 'dates', 'name': 'Dates'} + tabs.primitive_delete(course, 1) assert {'type': 'progress'} not in course.tabs # Check that dates has shifted up - assert course.tabs[2] == {'type': 'dates', 'name': 'Dates'} + assert course.tabs[1] == {'type': 'dates', 'name': 'Dates'} def test_insert(self): """Test primitive tab insertion.""" diff --git a/cms/envs/common.py b/cms/envs/common.py index 876fe559aa..5ae71ecce4 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1661,9 +1661,6 @@ INSTALLED_APPS = [ # edx-milestones service 'milestones', - # Self-paced course configuration - 'openedx.core.djangoapps.self_paced', - # Coursegraph 'cms.djangoapps.coursegraph.apps.CoursegraphConfig', diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py index 3cbaaf018e..4bc4fd5522 100644 --- a/common/djangoapps/util/views.py +++ b/common/djangoapps/util/views.py @@ -167,11 +167,6 @@ def calculate(request): return HttpResponse(json.dumps({'result': str(result)})) # lint-amnesty, pylint: disable=http-response-with-json-dumps -def info(request): - """ Info page (link from main header) """ - return render_to_response("info.html", {}) - - def add_p3p_header(view_func): """ This decorator should only be used with views which may be displayed through the iframe. diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 8136ad3c50..417b9060cc 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -532,14 +532,6 @@ class CourseFields: # lint-amnesty, pylint: disable=missing-class-docstring scope=Scope.settings ) has_children = True - info_sidebar_name = String( - display_name=_("Course Home Sidebar Name"), - help=_( - "Enter the heading that you want students to see above your course handouts on the Course Home page. " - "Your course handouts appear in the right panel of the page." - ), - deprecated=True, - scope=Scope.settings, default=_('Course Handouts')) show_timezone = Boolean( help=_( "True if timezones should be shown on dates in the course. " diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py index 55623b217e..4b6331589e 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py @@ -97,7 +97,6 @@ class SplitModuleTest(unittest.TestCase): "fields": { "tabs": [ CourseTab.load('courseware'), - CourseTab.load('course_info'), CourseTab.load('discussion'), CourseTab.load('wiki'), ], @@ -147,7 +146,6 @@ class SplitModuleTest(unittest.TestCase): "end": _date_field.from_json("2013-04-13T04:30"), "tabs": [ CourseTab.load('courseware'), - CourseTab.load('course_info'), CourseTab.load('discussion'), CourseTab.load('wiki'), CourseTab.load( @@ -320,7 +318,6 @@ class SplitModuleTest(unittest.TestCase): "fields": { "tabs": [ CourseTab.load('courseware'), - CourseTab.load('course_info'), CourseTab.load('discussion'), CourseTab.load('wiki'), ], @@ -417,7 +414,6 @@ class SplitModuleTest(unittest.TestCase): "fields": { "tabs": [ CourseTab.load('courseware'), - CourseTab.load('course_info'), CourseTab.load('discussion'), CourseTab.load('wiki'), ], @@ -581,7 +577,7 @@ class SplitModuleCourseTests(SplitModuleTest): course = self.findByIdInResult(courses, "head12345") assert course.location.org == 'testx' assert course.category == 'course', 'wrong category' - assert len(course.tabs) == 6, 'wrong number of tabs' + assert len(course.tabs) == 5, 'wrong number of tabs' assert course.display_name == 'The Ancient Greek Hero', 'wrong display name' assert course.advertised_start == 'Fall 2013', 'advertised_start' assert len(course.children) == 4, 'children' @@ -635,7 +631,7 @@ class SplitModuleCourseTests(SplitModuleTest): assert course.location.course_key.org == 'testx' assert course.location.course_key.course == 'wonderful' assert course.category == 'course', 'wrong category' - assert len(course.tabs) == 4, 'wrong number of tabs' + assert len(course.tabs) == 3, 'wrong number of tabs' assert course.display_name == 'The most wonderful course', course.display_name assert course.advertised_start is None assert len(course.children) == 0, 'children' @@ -665,7 +661,7 @@ class SplitModuleCourseTests(SplitModuleTest): assert course.location.course_key.org is None assert course.location.version_guid == head_course.previous_version assert course.category == 'course' - assert len(course.tabs) == 6 + assert len(course.tabs) == 5 assert course.display_name == 'The Ancient Greek Hero' assert course.graceperiod == datetime.timedelta(hours=2) assert course.advertised_start is None @@ -681,7 +677,7 @@ class SplitModuleCourseTests(SplitModuleTest): assert course.location.course_key.course == 'GreekHero' assert course.location.course_key.run == 'run' assert course.category == 'course' - assert len(course.tabs) == 6 + assert len(course.tabs) == 5 assert course.display_name == 'The Ancient Greek Hero' assert course.advertised_start == 'Fall 2013' assert len(course.children) == 4 @@ -933,7 +929,7 @@ class SplitModuleItemTests(SplitModuleTest): assert block.location.org == 'testx' assert block.location.course == 'GreekHero' assert block.location.run == 'run' - assert len(block.tabs) == 6, 'wrong number of tabs' + assert len(block.tabs) == 5, 'wrong number of tabs' assert block.display_name == 'The Ancient Greek Hero' assert block.advertised_start == 'Fall 2013' assert len(block.children) == 4 diff --git a/common/lib/xmodule/xmodule/tabs.py b/common/lib/xmodule/xmodule/tabs.py index 1292db60ac..56c3faaf08 100644 --- a/common/lib/xmodule/xmodule/tabs.py +++ b/common/lib/xmodule/xmodule/tabs.py @@ -382,7 +382,6 @@ class CourseTabList(List): within the course. """ course_tabs = [ - CourseTab.load('course_info'), CourseTab.load('courseware') ] @@ -481,14 +480,18 @@ class CourseTabList(List): @classmethod def upgrade_tabs(cls, tabs): """ - Reverse and Rename Courseware to Course and Course Info to Home Tabs. + Remove course_info tab, and rename courseware tab to Course if needed. """ if tabs and len(tabs) > 1: + # Reverse them so that course_info is first, and rename courseware to Course if tabs[0].get('type') == 'courseware' and tabs[1].get('type') == 'course_info': tabs[0], tabs[1] = tabs[1], tabs[0] - tabs[0]['name'] = _('Home') tabs[1]['name'] = _('Course') + # NOTE: this check used for legacy courses containing the course_info tab. course_info + # should be removed according to https://github.com/openedx/public-engineering/issues/56. + if tabs[0].get('type') == 'course_info': + tabs.pop(0) return tabs @classmethod @@ -499,22 +502,15 @@ class CourseTabList(List): Specific rules checked: - if no tabs specified, that's fine - - if tabs specified, first two must have type 'courseware' and 'course_info', in that order. + - if tabs specified, first must have type 'courseware'. """ if tabs is None or len(tabs) == 0: return - if len(tabs) < 2: - raise InvalidTabsException(f"Expected at least two tabs. tabs: '{tabs}'") - - if tabs[0].get('type') != 'course_info': + if tabs[0].get('type') != 'courseware': raise InvalidTabsException( - f"Expected first tab to have type 'course_info'. tabs: '{tabs}'") - - if tabs[1].get('type') != 'courseware': - raise InvalidTabsException( - f"Expected second tab to have type 'courseware'. tabs: '{tabs}'") + f"Expected first tab to have type 'courseware'. tabs: '{tabs}'") # the following tabs should appear only once # TODO: don't import openedx capabilities from common diff --git a/common/lib/xmodule/xmodule/tests/test_tabs.py b/common/lib/xmodule/xmodule/tests/test_tabs.py new file mode 100644 index 0000000000..b38292d659 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_tabs.py @@ -0,0 +1,117 @@ +""" +Tests for CourseTabsListTestCase. +""" +from unittest import TestCase +import ddt + +from xmodule.tabs import CourseTabList, InvalidTabsException + + +@ddt.ddt +class CourseTabsListTestCase(TestCase): + """ + Class containing CourseTabsListTestCase tests. + """ + + @ddt.data( + [ + [], + [] + ], + [ + [ + {'type': 'courseware', 'course_staff_only': False, 'name': 'Courseware'}, + {'type': 'course_info', 'course_staff_only': False, 'name': 'Course Info'}, + {'type': 'discussion', 'course_staff_only': False, 'name': 'Discussion'}, + {'type': 'wiki', 'course_staff_only': False, 'name': 'Wiki'}, + {'type': 'textbooks', 'course_staff_only': False, 'name': 'Textbooks'}, + {'type': 'progress', 'course_staff_only': False, 'name': 'Progress'} + ], + [ + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + {'type': 'discussion', 'course_staff_only': False, 'name': 'Discussion'}, + {'type': 'wiki', 'course_staff_only': False, 'name': 'Wiki'}, + {'type': 'textbooks', 'course_staff_only': False, 'name': 'Textbooks'}, + {'type': 'progress', 'course_staff_only': False, 'name': 'Progress'} + ], + ], + [ + [ + {'type': 'course_info', 'course_staff_only': False, 'name': 'Home'}, + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + {'type': 'discussion', 'course_staff_only': False, 'name': 'Discussion'}, + {'type': 'wiki', 'course_staff_only': False, 'name': 'Wiki'}, + {'type': 'textbooks', 'course_staff_only': False, 'name': 'Textbooks'}, + {'type': 'progress', 'course_staff_only': False, 'name': 'Progress'} + ], + [ + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + {'type': 'discussion', 'course_staff_only': False, 'name': 'Discussion'}, + {'type': 'wiki', 'course_staff_only': False, 'name': 'Wiki'}, + {'type': 'textbooks', 'course_staff_only': False, 'name': 'Textbooks'}, + {'type': 'progress', 'course_staff_only': False, 'name': 'Progress'} + ], + ] + ) + @ddt.unpack + def test_upgrade_tabs(self, tabs, expected_result): + CourseTabList.upgrade_tabs(tabs) + self.assertEqual(tabs, expected_result) + + @ddt.data( + [ + [], + True + ], + [ + [ + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + ], + True + ], + [ + [ + {'type': 'course_info', 'course_staff_only': False, 'name': 'Home'}, + ], + False + ], + [ + [ + {'type': 'course_info', 'course_staff_only': False, 'name': 'Home'}, + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + {'type': 'discussion', 'course_staff_only': False, 'name': 'Discussion'}, + {'type': 'wiki', 'course_staff_only': False, 'name': 'Wiki'}, + {'type': 'textbooks', 'course_staff_only': False, 'name': 'Textbooks'}, + {'type': 'progress', 'course_staff_only': False, 'name': 'Progress'} + ], + False + ], + [ + [ + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + {'type': 'discussion', 'course_staff_only': False, 'name': 'Discussion'}, + {'type': 'wiki', 'course_staff_only': False, 'name': 'Wiki'}, + {'type': 'textbooks', 'course_staff_only': False, 'name': 'Textbooks'}, + {'type': 'progress', 'course_staff_only': False, 'name': 'Progress'} + ], + False + ], + [ + [ + {'type': 'courseware', 'course_staff_only': False, 'name': 'Course'}, + {'type': 'discussion', 'course_staff_only': False, 'name': 'Discussion'}, + {'type': 'wiki', 'course_staff_only': False, 'name': 'Wiki'}, + {'type': 'textbooks', 'course_staff_only': False, 'name': 'Textbooks'}, + {'type': 'progress', 'course_staff_only': False, 'name': 'Progress'} + ], + True + ] + ) + @ddt.unpack + def test_validate_tabs(self, tabs, expected_success): + if not expected_success: + with self.assertRaises(InvalidTabsException): + CourseTabList.validate_tabs(tabs) + else: + CourseTabList.validate_tabs(tabs) diff --git a/common/test/data/scoreable/policies/course/policy.json b/common/test/data/scoreable/policies/course/policy.json index e177e5d444..714366c0f0 100644 --- a/common/test/data/scoreable/policies/course/policy.json +++ b/common/test/data/scoreable/policies/course/policy.json @@ -27,10 +27,6 @@ "minimum_grade_credit": 0.8, "start": "2030-01-01T00:00:00Z", "tabs": [ - { - "name": "Home", - "type": "course_info" - }, { "name": "Course", "type": "courseware" diff --git a/common/test/test-theme/lms/templates/courseware/courses.html b/common/test/test-theme/lms/templates/courseware/courses.html index 0500a293df..c21f247d89 100644 --- a/common/test/test-theme/lms/templates/courseware/courses.html +++ b/common/test/test-theme/lms/templates/courseware/courses.html @@ -3,6 +3,6 @@ # Include template which does not exist in the theme. <%include file="/courseware/error-message.html" /> # Include template which is overriden in the theme. -<%include file="/courseware/info.html" /> +<%include file="/courseware/progress.html" /> # Include custom template which only exists in the theme. <%include file="/courseware/test-theme-custom.html" /> diff --git a/common/test/test-theme/lms/templates/courseware/info.html b/common/test/test-theme/lms/templates/courseware/info.html deleted file mode 100644 index b47b665532..0000000000 --- a/common/test/test-theme/lms/templates/courseware/info.html +++ /dev/null @@ -1,2 +0,0 @@ -<%page expression_filter="h"/> -

This overrides the courseware/info.html template.

diff --git a/common/test/test-theme/lms/templates/courseware/progress.html b/common/test/test-theme/lms/templates/courseware/progress.html new file mode 100644 index 0000000000..29c3baf550 --- /dev/null +++ b/common/test/test-theme/lms/templates/courseware/progress.html @@ -0,0 +1,2 @@ +<%page expression_filter="h"/> +

This overrides the courseware/progress.html template.

diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 7f774faf62..2a67b6454e 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -13,7 +13,7 @@ from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.entrance_exams import user_can_skip_entrance_exam from lms.djangoapps.course_home_api.toggles import course_home_mfe_progress_tab_is_active from openedx.core.lib.course_tabs import CourseTabPluginManager -from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, default_course_url +from openedx.features.course_experience import default_course_url from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url from common.djangoapps.student.models import CourseEnrollment @@ -50,28 +50,8 @@ class CoursewareTab(EnrolledTab): @classmethod def is_enabled(cls, course, user=None): """ - Returns true if this tab is enabled. + Courseware tabs are viewable to everyone, even anonymous users. """ - if DISABLE_UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id): - return super().is_enabled(course, user) - # If this is the unified course tab then it is always enabled - return True - - -class CourseInfoTab(CourseTab): - """ - The course info view. - """ - type = 'course_info' - title = gettext_noop('Home') - priority = 10 - view_name = 'info' - tab_id = 'info' - is_movable = False - is_default = False - - @classmethod - def is_enabled(cls, course, user=None): return True @@ -355,9 +335,6 @@ def get_course_tab_list(user, course): continue tab.name = _("Entrance Exam") tab.title = _("Entrance Exam") - # TODO: LEARNER-611 - once the course_info tab is removed, remove this code - if not DISABLE_UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id) and tab.type == 'course_info': - continue if tab.type == 'static_tab' and tab.course_staff_only and \ not bool(user and has_access(user, 'staff', course, course.id)): continue diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py deleted file mode 100644 index fc68e5f93b..0000000000 --- a/lms/djangoapps/courseware/tests/test_course_info.py +++ /dev/null @@ -1,417 +0,0 @@ -""" -Test the course_info xblock -""" - - -from datetime import datetime - -from unittest import mock -import ddt -from ccx_keys.locator import CCXLocator -from django.conf import settings -from django.http import QueryDict -from django.test.utils import override_settings -from django.urls import reverse -from edx_toggles.toggles.testutils import override_waffle_flag -from pyquery import PyQuery as pq -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls -from xmodule.modulestore.tests.utils import TEST_DATA_DIR -from xmodule.modulestore.xml_importer import import_course_from_xml - -from lms.djangoapps.ccx.tests.factories import CcxFactory -from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration -from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context -from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES -from openedx.features.content_type_gating.models import ContentTypeGatingConfig -from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG -from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.tests.factories import AdminFactory -from common.djangoapps.util.date_utils import strftime_localized - -from .helpers import LoginEnrollmentTestCase - -QUERY_COUNT_TABLE_IGNORELIST = WAFFLE_TABLES - - -@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) -class CourseInfoTestCase(EnterpriseTestConsentRequired, LoginEnrollmentTestCase, SharedModuleStoreTestCase): - """ - Tests for the Course Info page - """ - - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.course = CourseFactory.create() - cls.page = ItemFactory.create( - category="course_info", parent_location=cls.course.location, - data="OOGIE BLOOGIE", display_name="updates" - ) - - def test_logged_in_unenrolled(self): - self.setup_user() - url = reverse('info', args=[str(self.course.id)]) - resp = self.client.get(url) - self.assertContains(resp, "OOGIE BLOOGIE") - self.assertContains(resp, "You are not currently enrolled in this course") - - def test_logged_in_enrolled(self): - self.enroll(self.course) - url = reverse('info', args=[str(self.course.id)]) - resp = self.client.get(url) - assert b'You are not currently enrolled in this course' not in resp.content - - # TODO: LEARNER-611: If this is only tested under Course Info, does this need to move? - @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request') - def test_redirection_missing_enterprise_consent(self, mock_enterprise_customer_for_request): - """ - Verify that users viewing the course info who are enrolled, but have not provided - data sharing consent, are first redirected to a consent page, and then, once they've - provided consent, are able to view the course info. - """ - # ENT-924: Temporary solution to replace sensitive SSO usernames. - mock_enterprise_customer_for_request.return_value = None - - self.setup_user() - self.enroll(self.course) - - url = reverse('info', args=[str(self.course.id)]) - - self.verify_consent_required(self.client, url) # lint-amnesty, pylint: disable=no-value-for-parameter - - def test_anonymous_user(self): - url = reverse('info', args=[str(self.course.id)]) - resp = self.client.get(url) - assert resp.status_code == 200 - assert b'OOGIE BLOOGIE' not in resp.content - - def test_logged_in_not_enrolled(self): - self.setup_user() - url = reverse('info', args=[str(self.course.id)]) - self.client.get(url) - - # Check whether the user has been enrolled in the course. - # There was a bug in which users would be automatically enrolled - # with is_active=False (same as if they enrolled and immediately unenrolled). - # This verifies that the user doesn't have *any* enrollment record. - enrollment_exists = CourseEnrollment.objects.filter( - user=self.user, course_id=self.course.id - ).exists() - assert not enrollment_exists - - @mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False}) - def test_non_live_course(self): - """Ensure that a user accessing a non-live course sees a redirect to - the student dashboard, not a 404. - """ - self.setup_user() - self.enroll(self.course) - url = reverse('info', args=[str(self.course.id)]) - response = self.client.get(url) - start_date = strftime_localized(self.course.start, 'SHORT_DATE') - expected_params = QueryDict(mutable=True) - expected_params['notlive'] = start_date - expected_url = '{url}?{params}'.format( - url=reverse('dashboard'), - params=expected_params.urlencode() - ) - self.assertRedirects(response, expected_url) - - @mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False}) - @mock.patch("common.djangoapps.util.date_utils.strftime_localized") - def test_non_live_course_other_language(self, mock_strftime_localized): - """Ensure that a user accessing a non-live course sees a redirect to - the student dashboard, not a 404, even if the localized date is unicode - """ - self.setup_user() - self.enroll(self.course) - fake_unicode_start_time = "üñîçø∂é_ßtå®t_tîµé" - mock_strftime_localized.return_value = fake_unicode_start_time - - url = reverse('info', args=[str(self.course.id)]) - response = self.client.get(url) - expected_params = QueryDict(mutable=True) - expected_params['notlive'] = fake_unicode_start_time - expected_url = '{url}?{params}'.format( - url=reverse('dashboard'), - params=expected_params.urlencode() - ) - self.assertRedirects(response, expected_url) - - def test_nonexistent_course(self): - self.setup_user() - url = reverse('info', args=['not/a/course']) - response = self.client.get(url) - assert response.status_code == 404 - - -@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) -class CourseInfoLastAccessedTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): - """ - Tests of the CourseInfo last accessed link. - """ - - def setUp(self): - super().setUp() - self.course = CourseFactory.create() - self.page = ItemFactory.create( - category="course_info", parent_location=self.course.location, - data="OOGIE BLOOGIE", display_name="updates" - ) - - def test_last_accessed_courseware_not_shown(self): - """ - Test that the last accessed courseware link is not shown if there - is no course content. - """ - SelfPacedConfiguration(enable_course_home_improvements=True).save() - url = reverse('info', args=(str(self.course.id),)) - response = self.client.get(url) - content = pq(response.content) - assert content('.page-header-secondary a').length == 0 - - def get_resume_course_url(self, course_info_url): - """ - Retrieves course info page and returns the resume course url - or None if the button doesn't exist. - """ - info_page_response = self.client.get(course_info_url) - content = pq(info_page_response.content) - return content('.page-header-secondary .last-accessed-link').attr('href') - - def test_resume_course_visibility(self): - SelfPacedConfiguration(enable_course_home_improvements=True).save() - chapter = ItemFactory.create( - category="chapter", parent_location=self.course.location - ) - section = ItemFactory.create( - category='sequential', parent_location=chapter.location - ) - section_url = reverse( - 'courseware_section', - kwargs={ - 'section': section.url_name, - 'chapter': chapter.url_name, - 'course_id': self.course.id - } - ) - self.client.get(section_url) - info_url = reverse('info', args=(str(self.course.id),)) - - # Assuring a non-authenticated user cannot see the resume course button. - resume_course_url = self.get_resume_course_url(info_url) - assert resume_course_url is None - - # Assuring an unenrolled user cannot see the resume course button. - self.setup_user() - resume_course_url = self.get_resume_course_url(info_url) - assert resume_course_url is None - - # Assuring an enrolled user can see the resume course button. - self.enroll(self.course) - resume_course_url = self.get_resume_course_url(info_url) - assert resume_course_url == section_url - - -@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) -@ddt.ddt -class CourseInfoTitleTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): - """ - Tests of the CourseInfo page title site configuration options. - """ - def setUp(self): - super().setUp() - self.course = CourseFactory.create( - org="HogwartZ", - number="Potions_3", - display_organization="HogwartsX", - display_coursenumber="Potions101", - display_name="Introduction to Potions" - ) - - @ddt.data( - # Default site configuration shows course number, org, and display name as subtitle. - ({}, - "Welcome to HogwartsX's Potions101!", "Introduction to Potions"), - - # Show org in title - (dict(COURSE_HOMEPAGE_INVERT_TITLE=False, - COURSE_HOMEPAGE_SHOW_SUBTITLE=True, - COURSE_HOMEPAGE_SHOW_ORG=True), - "Welcome to HogwartsX's Potions101!", "Introduction to Potions"), - - # Don't show org in title - (dict(COURSE_HOMEPAGE_INVERT_TITLE=False, - COURSE_HOMEPAGE_SHOW_SUBTITLE=True, - COURSE_HOMEPAGE_SHOW_ORG=False), - "Welcome to Potions101!", "Introduction to Potions"), - - # Hide subtitle and org - (dict(COURSE_HOMEPAGE_INVERT_TITLE=False, - COURSE_HOMEPAGE_SHOW_SUBTITLE=False, - COURSE_HOMEPAGE_SHOW_ORG=False), - "Welcome to Potions101!", None), - - # Show display name as title, hide subtitle and org. - (dict(COURSE_HOMEPAGE_INVERT_TITLE=True, - COURSE_HOMEPAGE_SHOW_SUBTITLE=False, - COURSE_HOMEPAGE_SHOW_ORG=False), - "Welcome to Introduction to Potions!", None), - - # Show display name as title with org, hide subtitle. - (dict(COURSE_HOMEPAGE_INVERT_TITLE=True, - COURSE_HOMEPAGE_SHOW_SUBTITLE=False, - COURSE_HOMEPAGE_SHOW_ORG=True), - "Welcome to HogwartsX's Introduction to Potions!", None), - - # Show display name as title, hide org, and show course number as subtitle. - (dict(COURSE_HOMEPAGE_INVERT_TITLE=True, - COURSE_HOMEPAGE_SHOW_SUBTITLE=True, - COURSE_HOMEPAGE_SHOW_ORG=False), - "Welcome to Introduction to Potions!", 'Potions101'), - - # Show display name as title with org, and show course number as subtitle. - (dict(COURSE_HOMEPAGE_INVERT_TITLE=True, - COURSE_HOMEPAGE_SHOW_SUBTITLE=True, - COURSE_HOMEPAGE_SHOW_ORG=True), - "Welcome to HogwartsX's Introduction to Potions!", 'Potions101'), - ) - @ddt.unpack - def test_info_title(self, site_config, expected_title, expected_subtitle): - """ - Test the info page on a course with all the multiple display options - depeding on the current site configuration - """ - url = reverse('info', args=(str(self.course.id),)) - with with_site_configuration_context(configuration=site_config): - response = self.client.get(url) - - content = pq(response.content) - - assert expected_title == content('.page-title').contents()[0].strip() - - if expected_subtitle is None: - assert not content('.page-subtitle') - else: - assert expected_subtitle == content('.page-subtitle').contents()[0].strip() - - -@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) -class CourseInfoTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase): - """ - Test for unenrolled student tries to access ccx. - Note: Only CCX coach can enroll a student in CCX. In sum self-registration not allowed. - """ - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.course = CourseFactory.create() - - def setUp(self): - super().setUp() - - # Create ccx coach account - self.coach = coach = AdminFactory.create(password="test") - self.client.login(username=coach.username, password="test") - - def test_redirect_to_dashboard_unenrolled_ccx(self): - """ - Assert that when unenroll student tries to access ccx do not allow them self-register. - Redirect them to their student dashboard - """ - # create ccx - ccx = CcxFactory(course_id=self.course.id, coach=self.coach) - ccx_locator = CCXLocator.from_course_locator(self.course.id, str(ccx.id)) - - self.setup_user() - url = reverse('info', args=[ccx_locator]) - response = self.client.get(url) - expected = reverse('dashboard') - self.assertRedirects(response, expected, status_code=302, target_status_code=200) - - -@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) -class CourseInfoTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): - """ - Tests for the Course Info page for an XML course - """ - def setUp(self): - """ - Set up the tests - """ - super().setUp() - - # The following test course (which lives at common/test/data/2014) - # is closed; we're testing that a course info page still appears when - # the course is already closed - self.xml_course_key = self.store.make_course_key('edX', 'detached_pages', '2014') - import_course_from_xml( - self.store, - self.user.id, - TEST_DATA_DIR, - source_dirs=['2014'], - static_content_store=None, - target_id=self.xml_course_key, - raise_on_failure=True, - create_if_not_present=True, - ) - - # this text appears in that course's course info page - # common/test/data/2014/info/updates.html - self.xml_data = "course info 463139" - - @mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - def test_logged_in_xml(self): - self.setup_user() - url = reverse('info', args=[str(self.xml_course_key)]) - resp = self.client.get(url) - self.assertContains(resp, self.xml_data) - - @mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - def test_anonymous_user_xml(self): - url = reverse('info', args=[str(self.xml_course_key)]) - resp = self.client.get(url) - self.assertNotContains(resp, self.xml_data) - - -@override_settings(FEATURES=dict(settings.FEATURES, EMBARGO=False)) -@override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) -class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase): - """ - Tests for the info page of self-paced courses. - """ - ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache'] - - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.instructor_paced_course = CourseFactory.create(self_paced=False) - cls.self_paced_course = CourseFactory.create(self_paced=True) - - def setUp(self): - super().setUp() - ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) - - self.setup_user() - - def fetch_course_info_with_queries(self, course, sql_queries, mongo_queries): - """ - Fetch the given course's info page, asserting the number of SQL - and Mongo queries. - """ - url = reverse('info', args=[str(course.id)]) - with self.assertNumQueries(sql_queries, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST): - with check_mongo_calls(mongo_queries): - with mock.patch("openedx.core.djangoapps.theming.helpers.get_current_site", return_value=None): - resp = self.client.get(url) - assert resp.status_code == 200 - - def test_num_queries_instructor_paced(self): - # TODO: decrease query count as part of REVO-28 - self.fetch_course_info_with_queries(self.instructor_paced_course, 41, 2) - - def test_num_queries_self_paced(self): - # TODO: decrease query count as part of REVO-28 - self.fetch_course_info_with_queries(self.self_paced_course, 41, 2) diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index b428859c17..41cc22d8de 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -9,7 +9,6 @@ import crum import ddt from django.conf import settings from django.test import RequestFactory -from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from freezegun import freeze_time from pytz import utc @@ -19,7 +18,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory -from common.djangoapps.student.tests.factories import TEST_PASSWORD, CourseEnrollmentFactory, UserFactory +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION from lms.djangoapps.commerce.models import CommerceConfiguration from lms.djangoapps.courseware.courses import get_course_date_blocks @@ -43,7 +42,6 @@ from lms.djangoapps.verify_student.services import IDVerificationService from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory -from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_experience import RELATIVE_DATES_FLAG @@ -51,9 +49,6 @@ from openedx.features.course_experience import RELATIVE_DATES_FLAG @ddt.ddt class CourseDateSummaryTest(SharedModuleStoreTestCase): """Tests for course date summary blocks.""" - def setUp(self): - super().setUp() - SelfPacedConfiguration.objects.create(enable_course_home_improvements=True) def make_request(self, user): """ Creates a request """ @@ -63,17 +58,6 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): crum.set_current_request(request) return request - def test_course_info_feature_flag(self): - SelfPacedConfiguration(enable_course_home_improvements=False).save() - course = create_course_run() - user = create_user() - CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) - - self.client.login(username=user.username, password=TEST_PASSWORD) - url = reverse('info', args=(course.id,)) - response = self.client.get(url) - self.assertNotContains(response, 'date-summary', status_code=302) - # Tests for which blocks are enabled def assert_block_types(self, course, user, expected_blocks): """Assert that the enabled block types for this course are as expected.""" diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 6c10cc38db..dbfdece5d9 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -13,7 +13,6 @@ from operator import itemgetter # lint-amnesty, pylint: disable=wrong-import-or from django.conf import settings from django.test import TestCase, RequestFactory from django.urls import reverse -from edx_toggles.toggles.testutils import override_waffle_flag from pytz import UTC from xblock.runtime import DictKeyValueStore @@ -31,9 +30,7 @@ from lms.djangoapps.courseware.tests.helpers import ( ) from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY -from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.user_api.preferences.api import get_user_preference, set_user_preference -from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import StaffFactory from common.djangoapps.student.tests.factories import UserFactory @@ -108,18 +105,6 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, Mas ) return self.client.get(url) - def get_course_info_page(self): - """ - Returns the server response for course info page. - """ - url = reverse( - 'info', - kwargs={ - 'course_id': str(self.course.id), - } - ) - return self.client.get(url) - def get_progress_page(self): """ Returns the server response for progress page. @@ -351,27 +336,6 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi assert get_user_preference(user, LANGUAGE_KEY) == expected_language_code assert self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value == expected_language_code - @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) - @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - def test_masquerade_as_specific_user_on_self_paced(self): - """ - Test masquerading as a specific user for course info page when self paced configuration - "enable_course_home_improvements" flag is set - - Login as a staff user and visit course info page. - set masquerade to view same page as a specific student and revisit the course info page. - """ - # Log in as staff, and check we can see the info page. - self.login_staff() - response = self.get_course_info_page() - self.assertContains(response, "OOGIE BLOOGIE") - - # Masquerade as the student,enable the self paced configuration, and check we can see the info page. - SelfPacedConfiguration(enable_course_home_improvements=True).save() - self.update_masquerade(role='student', username=self.student_user.username) - response = self.get_course_info_page() - self.assertContains(response, "OOGIE BLOOGIE") - @ddt.data( 'john', # Non-unicode username 'fôô@bar', # Unicode username with @, which is what the ENABLE_UNICODE_USERNAME feature allows @@ -442,25 +406,6 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi self.get_courseware_page() self.assertExpectedLanguageInPreference(self.test_user, english_language_code) - @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) - @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - def test_masquerade_as_specific_student_course_info(self): - """ - Test masquerading as a specific user for course info page. - - We login with login_staff and check course info page content if it's working and then we - set masquerade to view same page as a specific student and test if it's working or not. - """ - # Log in as staff, and check we can see the info page. - self.login_staff() - content = self.get_course_info_page().content.decode('utf-8') - assert 'OOGIE BLOOGIE' in content - - # Masquerade as the student, and check we can see the info page. - self.update_masquerade(role='student', username=self.student_user.username) - content = self.get_course_info_page().content.decode('utf-8') - assert 'OOGIE BLOOGIE' in content - def test_masquerade_as_specific_student_progress(self): """ Test masquerading as a specific user for progress page. diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py index b159c20b84..eefb231141 100644 --- a/lms/djangoapps/courseware/tests/test_tabs.py +++ b/lms/djangoapps/courseware/tests/test_tabs.py @@ -10,9 +10,7 @@ from django.http import Http404 from django.urls import reverse from milestones.tests.utils import MilestonesTestCaseMixin -from edx_toggles.toggles.testutils import override_waffle_flag from lms.djangoapps.courseware.tabs import ( - CourseInfoTab, CoursewareTab, DatesTab, ExternalDiscussionCourseTab, @@ -24,7 +22,6 @@ from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from lms.djangoapps.courseware.views.views import StaticCourseTabView, get_static_tab_fragment from openedx.core.djangolib.testing.utils import get_mock_request from openedx.core.lib.courses import get_course_by_id -from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import InstructorFactory from common.djangoapps.student.tests.factories import StaffFactory @@ -496,20 +493,16 @@ class TabListTestCase(TabTestCase): # invalid tabs self.invalid_tabs = [ - # less than 2 tabs - [{'type': CoursewareTab.type}], - # missing course_info - [{'type': CoursewareTab.type}, {'type': 'discussion', 'name': 'fake_name'}], + # missing courseware [{'type': 'unknown_type'}], # incorrect order [{'type': 'discussion', 'name': 'fake_name'}, - {'type': CourseInfoTab.type, 'name': 'fake_name'}, {'type': CoursewareTab.type}], + {'type': CoursewareTab.type}], ] # tab types that should appear only once unique_tab_types = [ CoursewareTab.type, - CourseInfoTab.type, 'textbooks', 'pdf_textbooks', 'html_textbooks', @@ -518,7 +511,6 @@ class TabListTestCase(TabTestCase): for unique_tab_type in unique_tab_types: self.invalid_tabs.append([ {'type': CoursewareTab.type}, - {'type': CourseInfoTab.type, 'name': 'fake_name'}, # add the unique tab multiple times {'type': unique_tab_type}, {'type': unique_tab_type}, @@ -532,7 +524,6 @@ class TabListTestCase(TabTestCase): # all valid tabs [ {'type': CoursewareTab.type}, - {'type': CourseInfoTab.type, 'name': 'fake_name'}, {'type': DatesTab.type}, # Add this even though we filter it out, for testing purposes {'type': 'discussion', 'name': 'fake_name'}, {'type': ExternalLinkCourseTab.type, 'name': 'fake_name', 'link': 'fake_link'}, @@ -547,7 +538,6 @@ class TabListTestCase(TabTestCase): # with external discussion [ {'type': CoursewareTab.type}, - {'type': CourseInfoTab.type, 'name': 'fake_name'}, {'type': ExternalDiscussionCourseTab.type, 'name': 'fake_name', 'link': 'fake_link'} ], ] @@ -575,8 +565,7 @@ class ValidateTabsTestCase(TabListTestCase): """ tab_list = xmodule_tabs.CourseTabList() assert len(tab_list.from_json([{'type': CoursewareTab.type}, - {'type': CourseInfoTab.type, 'name': 'fake_name'}, - {'type': 'no_such_type'}])) == 2 + {'type': 'no_such_type'}])) == 1 class CourseTabListTestCase(TabListTestCase): @@ -746,33 +735,6 @@ class StaticTabTestCase(TabTestCase): self.check_get_and_set_method_for_key(tab, 'url_slug') -class CourseInfoTabTestCase(TabTestCase): - """Test cases for the course info tab.""" - def setUp(self): # lint-amnesty, pylint: disable=super-method-not-called - self.user = self.create_mock_user() - self.addCleanup(set_current_request, None) - - @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) - def test_default_tab(self): - # Verify that the course info tab is the first tab - tabs = get_course_tab_list(self.user, self.course) - assert tabs[0].type == 'course_info' - - @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=False) - def test_default_tab_for_new_course_experience(self): - # Verify that the unified course experience hides the course info tab - tabs = get_course_tab_list(self.user, self.course) - assert tabs[0].type == 'courseware' - - # TODO: LEARNER-611 - remove once course_info is removed. - @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=False) - def test_default_tab_for_displayable(self): - tabs = xmodule_tabs.CourseTabList.iterate_displayable(self.course, self.user) - for i, tab in enumerate(tabs): - if i == 0: - assert tab.type == 'course_info' - - class DiscussionLinkTestCase(TabTestCase): """Test cases for discussion link tab.""" diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 0db2441ef0..2ffdc46691 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -91,7 +91,6 @@ from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_experience import ( DISABLE_COURSE_OUTLINE_PAGE_FLAG, - DISABLE_UNIFIED_COURSE_TAB_FLAG, ) from openedx.features.course_experience.tests.views.helpers import add_course_mode from openedx.features.course_experience.url_helpers import ( @@ -1022,19 +1021,6 @@ class ViewsTestCase(BaseViewsTestCase): response = self.client.get(url) self.assertRedirects(response, reverse('signin_user') + '?next=' + url) - @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) - def test_bypass_course_info(self): - course_id = str(self.course_key) - - response = self.client.get(reverse('info', args=[course_id])) - assert response.status_code == 200 - - response = self.client.get(reverse('info', args=[course_id]), HTTP_REFERER=reverse('dashboard')) - assert response.status_code == 200 - - response = self.client.get(reverse('info', args=[course_id]), HTTP_REFERER='foo') - assert response.status_code == 200 - # Patching 'lms.djangoapps.courseware.views.views.get_programs' would be ideal, # but for some unknown reason that patch doesn't seem to be applied. diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 031a8b75b5..58387c56cf 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -67,7 +67,7 @@ from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.course_goals.models import UserActivity from lms.djangoapps.course_home_api.toggles import course_home_mfe_progress_tab_is_active from lms.djangoapps.courseware.access import has_access, has_ccx_coach_role -from lms.djangoapps.courseware.access_utils import check_course_open_for_learner, check_public_access +from lms.djangoapps.courseware.access_utils import check_public_access from lms.djangoapps.courseware.courses import ( can_self_enroll_in_course, course_open_for_self_enrollment, @@ -75,7 +75,6 @@ from lms.djangoapps.courseware.courses import ( get_course_overview_with_access, get_course_with_access, get_courses, - get_current_child, get_permission_for_course_about, get_studio_url, sort_by_announcement, @@ -113,7 +112,6 @@ from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender -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.djangoapps.zendesk_proxy.utils import create_zendesk_ticket @@ -121,19 +119,16 @@ from openedx.core.djangolib.markup import HTML, Text from openedx.core.lib.courses import get_course_by_id from openedx.core.lib.mobile_utils import is_request_from_mobile_app from openedx.features.course_duration_limits.access import generate_course_expired_fragment -from openedx.features.course_experience import DISABLE_UNIFIED_COURSE_TAB_FLAG, course_home_url -from openedx.features.course_experience.course_tools import CourseToolsPluginManager +from openedx.features.course_experience import course_home_url from openedx.features.course_experience.url_helpers import ( get_courseware_url, get_learning_mfe_home_url, is_request_from_learning_mfe ) from openedx.features.course_experience.utils import dates_banner_should_display -from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML from openedx.features.enterprise_support.api import data_sharing_consent_required -from ..entrance_exams import user_can_skip_entrance_exam from ..module_render import get_module, get_module_by_usage_id, get_module_for_descriptor from ..tabs import _get_dynamic_tabs from ..toggles import COURSEWARE_OPTIMIZED_RENDER_XBLOCK @@ -436,149 +431,6 @@ def jump_to(request, course_id, location): return redirect(redirect_url) -@ensure_csrf_cookie -@ensure_valid_course_key -@data_sharing_consent_required -def course_info(request, course_id): - """ - Display the course's info.html, or 404 if there is no such course. - Assumes the course_id is in a valid format. - """ - # TODO: LEARNER-611: This can be deleted with Course Info removal. The new - # Course Home is using its own processing of last accessed. - def get_last_accessed_courseware(course, request, user): - """ - Returns the courseware module URL that the user last accessed, or None if it cannot be found. - """ - field_data_cache = FieldDataCache.cache_for_descriptor_descendents( - course.id, request.user, course, depth=2 - ) - course_module = get_module_for_descriptor( - user, - request, - course, - field_data_cache, - course.id, - course=course, - will_recheck_access=True, - ) - chapter_module = get_current_child(course_module) - if chapter_module is not None: - section_module = get_current_child(chapter_module) - if section_module is not None: - url = reverse('courseware_section', kwargs={ - 'course_id': str(course.id), - 'chapter': chapter_module.url_name, - 'section': section_module.url_name - }) - return url - return None - - course_key = CourseKey.from_string(course_id) - - # If the unified course experience is enabled, redirect to the "Course" tab - if not DISABLE_UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key): - return redirect(course_home_url(course_key)) - - with modulestore().bulk_operations(course_key): - course = get_course_with_access(request.user, 'load', course_key) - - can_masquerade = request.user.has_perm(MASQUERADE_AS_STUDENT, course) - masquerade, user = setup_masquerade(request, course_key, can_masquerade, reset_masquerade_data=True) - - # LEARNER-612: CCX redirect handled by new Course Home (DONE) - # LEARNER-1697: Transition banner messages to new Course Home (DONE) - # if user is not enrolled in a course then app will show enroll/get register link inside course info page. - user_is_enrolled = CourseEnrollment.is_enrolled(user, course.id) - show_enroll_banner = request.user.is_authenticated and not user_is_enrolled - - # If the user is not enrolled but this is a course that does not support - # direct enrollment then redirect them to the dashboard. - if not user_is_enrolled and not can_self_enroll_in_course(course_key): - return redirect(reverse('dashboard')) - - # LEARNER-170: Entrance exam is handled by new Course Outline. (DONE) - # If the user needs to take an entrance exam to access this course, then we'll need - # to send them to that specific course module before allowing them into other areas - if not user_can_skip_entrance_exam(user, course): - return redirect(reverse('courseware', args=[str(course.id)])) - - # Construct the dates fragment - dates_fragment = None - - if request.user.is_authenticated: - # TODO: LEARNER-611: Remove enable_course_home_improvements - if SelfPacedConfiguration.current().enable_course_home_improvements: - # Shared code with the new Course Home (DONE) - dates_fragment = CourseDatesFragmentView().render_to_fragment(request, course_id=course_id) - - # Shared code with the new Course Home (DONE) - # Get the course tools enabled for this user and course - course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key) - - course_homepage_invert_title =\ - configuration_helpers.get_value( - 'COURSE_HOMEPAGE_INVERT_TITLE', - False - ) - - course_homepage_show_subtitle =\ - configuration_helpers.get_value( - 'COURSE_HOMEPAGE_SHOW_SUBTITLE', - True - ) - - course_homepage_show_org =\ - configuration_helpers.get_value('COURSE_HOMEPAGE_SHOW_ORG', True) - - course_title = course.display_number_with_default - course_subtitle = course.display_name_with_default - if course_homepage_invert_title: - course_title = course.display_name_with_default - course_subtitle = course.display_number_with_default - - context = { - 'request': request, - 'masquerade_user': user, - 'course_id': str(course_key), - 'url_to_enroll': CourseTabView.url_to_enroll(course_key), - 'cache': None, - 'course': course, - 'course_title': course_title, - 'course_subtitle': course_subtitle, - 'show_subtitle': course_homepage_show_subtitle, - 'show_org': course_homepage_show_org, - 'can_masquerade': can_masquerade, - 'masquerade': masquerade, - 'supports_preview_menu': True, - 'studio_url': get_studio_url(course, 'course_info'), - 'show_enroll_banner': show_enroll_banner, - 'user_is_enrolled': user_is_enrolled, - 'dates_fragment': dates_fragment, - 'course_tools': course_tools, - } - context.update( - get_experiment_user_metadata_context( - course, - user, - ) - ) - - # Get the URL of the user's last position in order to display the 'where you were last' message - context['resume_course_url'] = None - # TODO: LEARNER-611: Remove enable_course_home_improvements - if SelfPacedConfiguration.current().enable_course_home_improvements: - context['resume_course_url'] = get_last_accessed_courseware(course, request, user) - - if not check_course_open_for_learner(user, course): - # Disable student view button if user is staff and - # course is not yet visible to students. - context['disable_student_access'] = True - context['supports_preview_menu'] = False - - return render_to_response('courseware/info.html', context) - - class StaticCourseTabView(EdxFragmentView): """ View that displays a static course tab with a given name. diff --git a/lms/envs/common.py b/lms/envs/common.py index f268ad6b2a..1303f351f6 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -505,8 +505,7 @@ FEATURES = { # .. toggle_implementation: DjangoSetting # .. toggle_default: True # .. toggle_description: When enabled, along with the ENABLE_MKTG_SITE feature toggle, users who attempt to access a - # course "about" page will be redirected to the course home url. This url might be the course "info" page or the - # unified course tab (when the DISABLE_UNIFIED_COURSE_TAB_FLAG waffle is not enabled). + # course "about" page will be redirected to the course home url. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2019-01-15 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/19604 @@ -3148,9 +3147,6 @@ INSTALLED_APPS = [ # Catalog integration 'openedx.core.djangoapps.catalog', - # Self-paced course configuration - 'openedx.core.djangoapps.self_paced', - 'sorl.thumbnail', # edx-milestones service diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss deleted file mode 100644 index b1331985c6..0000000000 --- a/lms/static/sass/course/_info.scss +++ /dev/null @@ -1,386 +0,0 @@ -//// Notifications -// Upgrade - -$notification-highlight-border-color: $uxpl-green-base !default; -$notification-background: rgb(255, 255, 255) !default; - -.home { - @include clearfix(); - - max-width: map-get($container-max-widths, xl); - margin: 0 auto; - padding: $baseline $baseline ($baseline/2) $baseline; - - .page-header-main { - display: inline-block; - width: flex-grid(8, 12); - margin: 0; - - .page-title { - margin-bottom: 5px; - color: $dark-gray1; - text-transform: none; - } - - .page-subtitle { - color: $dark-gray1; - font-size: 14px; - text-transform: none; - } - } - - .page-header-secondary { - @include float(right); - - display: inline-block; - margin: ($baseline/2); - padding: ($baseline/2) ($baseline*0.75); - background-color: $blue; - border-radius: 2px; - - .last-accessed-link { - @extend %t-title6; - - color: $very-light-text; - } - } -} - -div.info-wrapper { - background-color: $homepage-background; - - section.updates { - @extend .content; - - @include padding-left($baseline); - - line-height: lh(); - width: 100%; - display: block; - - > p { - margin-bottom: lh(); - } - - > ol, - section, - div { - list-style: none; - margin-bottom: lh(); - padding-left: 0; - - .updates-article { - border-radius: 3px; - background-color: $white; - border: 1px solid transparent; - - &:hover { - border: 1px solid $gray-l3; - } - } - - .show-older-updates { - @extend %btn-pl-white-base; - - padding: ($baseline/2); - - @include font-size(14); - - width: 100%; - display: block; - text-align: center; - cursor: pointer; - background: none; - - &:hover, - &:focus { - background-color: unset; - color: $m-blue-d3; - border: 1px solid black; - } - } - - > li, - article { - @extend .clearfix; - - padding: $baseline; - list-style-type: none; - margin-bottom: lh(1.5); - background-color: $white; - - ol, - ul { - ol, - ul { - list-style-type: disc; - } - } - - .date { - @extend %t-title9; - - margin-bottom: ($baseline/4); - text-transform: none; - background: url('#{$static-path}/images/calendar-icon.png') 0 center no-repeat; - - @include padding-left($baseline); - @include float(left); - } - - - .toggle-visibility-button { - @extend %t-title9; - - @include float(right); - - padding: 0; - cursor: pointer; - background: none; - border: none; - color: $blue; - font-weight: normal; - } - - .toggle-visibility-element { - content: ''; - display: block; - clear: both; - } - - section.update-description { - section { - &.primary { - border: 1px solid #ddd; - background: $gray-l6; - padding: 20px; - - p { - font-weight: bold; - } - - .author { - font-weight: normal; - font-style: italic; - } - } - } - - h3 { - font-size: 1em; - font-weight: bold; - margin: lh(1.5) 0 lh(0.5); - } - - > ul { - list-style-type: disc; - } - - > ol { - list-style: decimal outside none; - padding: 0 0 0 1em; - } - - li { - margin-bottom: lh(0.5); - } - } - } - } - } - - section.handouts { - padding: 20px 30px; - margin: 0; - - @extend .sidebar; - - background: rgba(0, 0, 0, 0); - box-shadow: none; - font-size: 14px; - - a { - color: $link-color; - display: block; - font-size: 16px; - - span { - width: 20px; - text-align: center; - } - - &:not(:first-child) { - margin-top: 10px; - } - } - - &::after { - left: -1px; - right: auto; - } - - .handouts-header { - @include text-align(left); - - @extend %t-strong; - @extend %t-title6; - - margin-bottom: 0; - padding: 12px 26px 10px 0; - } - - ul { - margin-bottom: 14px; - } - - ol { - margin-bottom: 14px; - - li { - @include text-align(left); - - a { - display: block; - padding: 0; - color: $link-color; - - &:hover, - &:focus { - background: transparent; - } - } - - &.expandable, - &.collapsable { - margin: 0 16px 14px; - - @include transition(all 0.2s linear 0s); - - h4 { - color: $link-color; - font-size: 1em; - font-weight: normal; - padding-left: 30px; - } - } - - &.collapsable { - background: $white; - border-radius: 3px; - padding: 14px 0; - box-shadow: 0 0 1px 1px $shadow-l1, 0 1px 3px rgba(0, 0, 0, 0.25); - - h4 { - margin-bottom: 16px; - } - } - - &.multiple { - a { - display: inline-block; - padding: 0; - - &:hover, - &:focus { - background: transparent; - } - } - } - - ul { - background: none; - margin: 0; - - li { - border-bottom: 0; - border-top: 1px solid #e6e6e6; - font-size: 0.9em; - margin: 0; - padding: 15px 30px; - - a { - display: inline-block; - padding: 0; - - &:hover, - &:focus { - background: transparent; - } - } - } - } - - div.hitarea { - background-image: url('#{$static-path}/images/treeview-default.gif') no-repeat; - display: block; - height: 100%; - margin-left: 0; - max-height: 20px; - position: absolute; - width: 100%; - - &:hover, - &:focus { - opacity: 0.6; - filter: alpha(opacity=60); - - + h4 { - @extend a:hover; - - text-decoration: underline; - } - } - - &.expandable-hitarea { - background-position: -72px 0; - } - - &.collapsable-hitarea { - background-position: -55px -23px; - } - } - - h3 { - border-bottom: 0; - box-shadow: none; - color: #888; - font-size: 1em; - margin-bottom: 0; - } - - p { - letter-spacing: 0; - margin: 0; - text-transform: none; - - a { - padding-right: 8px; - - &::before { - color: $gray-l3; - content: "•"; - display: inline-block; - padding-right: 8px; - } - - &:first-child { - &::before { - content: ""; - padding-right: 0; - } - } - } - } - } - } - - @media print { - background: transparent !important; - } - } - - @media print { - background: transparent !important; - border: 0; - } -} diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html deleted file mode 100644 index 022ef48669..0000000000 --- a/lms/templates/courseware/info.html +++ /dev/null @@ -1,125 +0,0 @@ -<%page expression_filter="h"/> -<%inherit file="../main.html" /> -<%def name="online_help_token()"><% return "courseinfo" %> -<%namespace name='static' file='../static_content.html'/> -<%! -from datetime import datetime -from pytz import timezone, utc - -from django.urls import reverse -from django.utils.translation import ugettext as _ - -from lms.djangoapps.courseware.courses import get_course_info_section -from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration -from openedx.core.djangolib.markup import HTML, Text -%> - -<%block name="pagetitle">${_("{course_number} Course Info").format(course_number=course.display_number_with_default)} - -<%block name="headextra"> -<%static:css group='style-course-vendor'/> -<%static:css group='style-course'/> - - -% if show_enroll_banner: -
-
-
-

${_("You are not enrolled yet")}

-
-

- ${Text(_("You are not currently enrolled in this course. {link_start}Enroll now!{link_end}")).format( - link_start=HTML("").format(url_to_enroll), - link_end=HTML("") - )} -

-
-
-
-
-% endif - -<%include file="/courseware/course_navigation.html" args="active_page='info'" /> - -<%static:require_module_async module_name="js/courseware/toggle_element_visibility" class_name="ToggleElementVisibility"> - ToggleElementVisibility(); - -<%static:require_module_async module_name="js/courseware/course_info_events" class_name="CourseInfoEvents"> - CourseInfoEvents(); - - -<%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''} - -
-
-
-
-

- % if show_org: - ${_("Welcome to {org}'s {course_title}!").format(org=course.display_org_with_default, course_title=course_title)} - % else: - ${_("Welcome to {course_title}!").format(course_title=course_title)} - % endif - % if show_subtitle: -
${course_subtitle}
- % endif -

-
- % if resume_course_url and user_is_enrolled: - - % endif -
-
- % if user.is_authenticated: -
- % if studio_url is not None and masquerade and masquerade.role == 'staff': - - % endif - -

${_("Course Updates and News")}

- ${HTML(get_course_info_section(request, masquerade_user, course, 'updates'))} - -
-
- % if course_tools: -

${_("Course Tools")}

- % for course_tool in course_tools: - - - ${course_tool.title()} - - % endfor - % endif - % if SelfPacedConfiguration.current().enable_course_home_improvements: - ${HTML(dates_fragment.body_html())} - % endif -

${_(course.info_sidebar_name)}

- ${HTML(get_course_info_section(request, masquerade_user, course, 'handouts'))} -
- % else: -
-

${_("Course Updates and News")}

- ${HTML(get_course_info_section(request, masquerade_user, course, 'guest_updates'))} -
-
-

${_("Course Handouts")}

- ${HTML(get_course_info_section(request, masquerade_user, course, 'guest_handouts'))} -
- % endif -
-
-
- -<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory"> - DateUtilFactory.transform(iterationKey=".localized-datetime"); - diff --git a/lms/urls.py b/lms/urls.py index 7c9e08a745..48f3e00ba2 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -47,7 +47,6 @@ from openedx.core.djangoapps.password_policy import compliance as password_polic from openedx.core.djangoapps.password_policy.forms import PasswordPolicyAwareAdminAuthForm from openedx.core.djangoapps.plugins.constants import ProjectType from openedx.core.djangoapps.programs.models import ProgramsApiConfig -from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.user_authn.views.login import redirect_to_lms_login from openedx.features.enterprise_support.api import enterprise_enabled @@ -379,16 +378,9 @@ urlpatterns += [ r'^courses/{}/$'.format( settings.COURSE_ID_PATTERN, ), - courseware_views.course_info, + courseware_views.course_about, name='course_root', ), - re_path( - r'^courses/{}/info$'.format( - settings.COURSE_ID_PATTERN, - ), - courseware_views.course_info, - name='info', - ), # TODO arjun remove when custom tabs in place, see courseware/courses.py re_path( r'^courses/{}/syllabus$'.format( @@ -892,7 +884,6 @@ if settings.FEATURES.get('ENABLE_LTI_PROVIDER'): ] urlpatterns += [ - path('config/self_paced', ConfigurationModelCurrentAPIView.as_view(model=SelfPacedConfiguration)), path('config/programs', ConfigurationModelCurrentAPIView.as_view(model=ProgramsApiConfig)), path('config/catalog', ConfigurationModelCurrentAPIView.as_view(model=CatalogIntegration)), path('config/forums', ConfigurationModelCurrentAPIView.as_view(model=ForumsConfig)), diff --git a/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py b/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py index d9b8859a60..3af01b49a3 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py +++ b/openedx/core/djangoapps/content/course_overviews/tests/test_course_overviews.py @@ -62,7 +62,7 @@ class CourseOverviewTestCase(CatalogIntegrationMixin, ModuleStoreTestCase, Cache None: None, } - COURSE_OVERVIEW_TABS = {'courseware', 'info', 'textbooks', 'discussion', 'wiki', 'progress', 'dates'} + COURSE_OVERVIEW_TABS = {'courseware', 'textbooks', 'discussion', 'wiki', 'progress', 'dates'} ENABLED_SIGNALS = ['course_deleted', 'course_published'] diff --git a/openedx/core/djangoapps/schedules/docs/README.rst b/openedx/core/djangoapps/schedules/docs/README.rst index 9b662eb7bb..4e9f02ca39 100644 --- a/openedx/core/djangoapps/schedules/docs/README.rst +++ b/openedx/core/djangoapps/schedules/docs/README.rst @@ -236,17 +236,6 @@ Configuration Flags Configuring Schedule Creation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Self-paced Configuration -^^^^^^^^^^^^^^^^^^^^^^^^ - -Schedules will only be created for a course if it is self-paced. A -course can be configured to be self-paced by going to -``/admin/self_paced/selfpacedconfiguration/`` and adding an -enabled self paced config. Then, go to Studio settings for the course -and change the Course Pacing value to “Self-Paced”. Note that the Course -Start Date has to be set to sometime in the future in order to change -the Course Pacing. - Configuring Upgrade Deadline on Schedule ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/openedx/core/djangoapps/schedules/tests/test_resolvers.py b/openedx/core/djangoapps/schedules/tests/test_resolvers.py index 83b98e5b76..2557c3c961 100644 --- a/openedx/core/djangoapps/schedules/tests/test_resolvers.py +++ b/openedx/core/djangoapps/schedules/tests/test_resolvers.py @@ -252,7 +252,7 @@ class TestCourseNextSectionUpdateResolver(SchedulesResolverTestMixin, ModuleStor def test_schedule_context(self): resolver = self.create_resolver() # using this to make sure the select_related stays intact - with self.assertNumQueries(41): + with self.assertNumQueries(38): sc = resolver.get_schedules() schedules = list(sc) diff --git a/openedx/core/djangoapps/self_paced/__init__.py b/openedx/core/djangoapps/self_paced/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openedx/core/djangoapps/self_paced/admin.py b/openedx/core/djangoapps/self_paced/admin.py deleted file mode 100644 index df3ff01b25..0000000000 --- a/openedx/core/djangoapps/self_paced/admin.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Admin site bindings for self-paced courses. -""" - - -from config_models.admin import ConfigurationModelAdmin -from django.contrib import admin - -from .models import SelfPacedConfiguration - -admin.site.register(SelfPacedConfiguration, ConfigurationModelAdmin) diff --git a/openedx/core/djangoapps/self_paced/migrations/0001_initial.py b/openedx/core/djangoapps/self_paced/migrations/0001_initial.py deleted file mode 100644 index 0e8c85408d..0000000000 --- a/openedx/core/djangoapps/self_paced/migrations/0001_initial.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.db import migrations, models -import django.db.models.deletion -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='SelfPacedConfiguration', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), - ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), - ('enable_course_home_improvements', models.BooleanField(default=False, verbose_name='Enable course home page improvements.')), - ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')), - ], - options={ - 'ordering': ('-change_date',), - 'abstract': False, - }, - ), - ] diff --git a/openedx/core/djangoapps/self_paced/migrations/__init__.py b/openedx/core/djangoapps/self_paced/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openedx/core/djangoapps/self_paced/models.py b/openedx/core/djangoapps/self_paced/models.py deleted file mode 100644 index 333214a9c6..0000000000 --- a/openedx/core/djangoapps/self_paced/models.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Configuration for self-paced courses. -""" - - -from config_models.models import ConfigurationModel -from django.db.models import BooleanField -from django.utils.translation import gettext_lazy as _ - - -class SelfPacedConfiguration(ConfigurationModel): - """ - Configuration for self-paced courses. - - .. no_pii: - """ - - enable_course_home_improvements = BooleanField( - default=False, - verbose_name=_("Enable course home page improvements.") - ) diff --git a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py b/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py index 0971ac0415..21b9ba952a 100644 --- a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py +++ b/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py @@ -124,8 +124,8 @@ class TestComprehensiveThemeLMS(TestCase): courses_url = reverse('courses') resp = self.client.get(courses_url) assert resp.status_code == 200 - # The courses.html template includes the info.html file, which is overriden in the theme. - self.assertContains(resp, "This overrides the courseware/info.html template.") + # The courses.html template includes the progress.html file, which is overriden in the theme. + self.assertContains(resp, "This overrides the courseware/progress.html template.") @with_comprehensive_theme("test-theme") def test_include_custom_template(self): diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py index e8c0e6ac64..52b870033a 100644 --- a/openedx/features/course_experience/__init__.py +++ b/openedx/features/course_experience/__init__.py @@ -16,11 +16,6 @@ DISABLE_COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: di f'{WAFFLE_FLAG_NAMESPACE}.disable_course_outline_page', __name__ ) -# Waffle flag to enable a single unified "Course" tab. -DISABLE_UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - f'{WAFFLE_FLAG_NAMESPACE}.disable_unified_course_tab', __name__ -) - # Waffle flag to enable the sock on the footer of the home and courseware pages. DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.display_course_sock', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation @@ -100,8 +95,4 @@ def course_home_url(course_key): course_key (CourseKey): The course key for which the home url is being requested. """ from .url_helpers import get_learning_mfe_home_url - - if DISABLE_UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key): - return reverse('info', args=[str(course_key)]) - return get_learning_mfe_home_url(course_key, url_fragment='home') diff --git a/openedx/features/course_experience/plugins.py b/openedx/features/course_experience/plugins.py index 1d3adab33c..35f0cea023 100644 --- a/openedx/features/course_experience/plugins.py +++ b/openedx/features/course_experience/plugins.py @@ -11,7 +11,6 @@ from django.utils.translation import gettext as _ from common.djangoapps.student.models import CourseEnrollment from openedx.core.lib.courses import get_course_by_id -from . import DISABLE_UNIFIED_COURSE_TAB_FLAG from .course_tools import CourseTool from .views.course_updates import CourseUpdatesFragmentView @@ -46,8 +45,6 @@ class CourseUpdatesTool(CourseTool): """ Returns True if the user should be shown course updates for this course. """ - if DISABLE_UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key): - return False if not CourseEnrollment.is_enrolled(request.user, course_key): return False course = get_course_by_id(course_key) diff --git a/openedx/features/enterprise_support/tests/test_api.py b/openedx/features/enterprise_support/tests/test_api.py index 08e108a67e..714c4179a9 100644 --- a/openedx/features/enterprise_support/tests/test_api.py +++ b/openedx/features/enterprise_support/tests/test_api.py @@ -704,9 +704,12 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase): ) course_id = 'course-v1:edX+DemoX+Demo_Course' - return_to = None if is_return_to_null else 'info' + return_to = None if is_return_to_null else 'courseware' - expected_path = request_mock.path if is_return_to_null else '/courses/course-v1:edX+DemoX+Demo_Course/info' + if is_return_to_null: + expected_path = request_mock.path + else: + expected_path = '/courses/course-v1:edX+DemoX+Demo_Course/courseware' expected_url_args = { 'course_id': ['course-v1:edX+DemoX+Demo_Course'], 'failure_url': ['http://localhost:8000/dashboard?consent_failed=course-v1%3AedX%2BDemoX%2BDemo_Course'], diff --git a/setup.py b/setup.py index ef1d2fc033..e67f8040ef 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,6 @@ setup( "openedx.course_tab": [ "ccx = lms.djangoapps.ccx.plugins:CcxCourseTab", "courseware = lms.djangoapps.courseware.tabs:CoursewareTab", - "course_info = lms.djangoapps.courseware.tabs:CourseInfoTab", "dates = lms.djangoapps.courseware.tabs:DatesTab", "discussion = lms.djangoapps.discussion.plugins:DiscussionTab", "edxnotes = lms.djangoapps.edxnotes.plugins:EdxNotesTab",