refactor: Remove legacy course info page & related code

This commit is contained in:
Sagirov Eugeniy
2022-04-18 16:50:51 +03:00
parent 7f5d8e3511
commit 92ca176fde
41 changed files with 176 additions and 1406 deletions

View File

@@ -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

View File

@@ -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/",

View File

@@ -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 <id> required, e.g. Stanford/CS99/2013_spring'
delete_help = '--delete <tab-number>'
insert_help = '--insert <tab-number> <type> <name>, e.g. 4 "course_info" "Course Info"'
insert_help = '--insert <tab-number> <type> <name>, e.g. 4 "discussion" "Discussion"'
def add_arguments(self, parser):
parser.add_argument('--course',

View File

@@ -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}'

View File

@@ -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)

View File

@@ -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)')

View File

@@ -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."""

View File

@@ -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',

View File

@@ -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.

View File

@@ -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. "

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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" />

View File

@@ -1,2 +0,0 @@
<%page expression_filter="h"/>
<p>This overrides the courseware/info.html template.</p>

View File

@@ -0,0 +1,2 @@
<%page expression_filter="h"/>
<p>This overrides the courseware/progress.html template.</p>

View File

@@ -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

View File

@@ -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)

View File

@@ -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."""

View File

@@ -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.

View File

@@ -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."""

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -1,125 +0,0 @@
<%page expression_filter="h"/>
<%inherit file="../main.html" />
<%def name="online_help_token()"><% return "courseinfo" %></%def>
<%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>
<%block name="headextra">
<%static:css group='style-course-vendor'/>
<%static:css group='style-course'/>
</%block>
% if show_enroll_banner:
<div class="wrapper-msg urgency-low" id="failed-verification-banner">
<div class="msg msg-reverify is-dismissable">
<div class="msg-content">
<h2 class="title">${_("You are not enrolled yet")}</h2>
<div class="copy">
<p class='enroll-message'>
${Text(_("You are not currently enrolled in this course. {link_start}Enroll now!{link_end}")).format(
link_start=HTML("<a href={}>").format(url_to_enroll),
link_end=HTML("</a>")
)}
</p>
</div>
</div>
</div>
</div>
% 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>
<%static:require_module_async module_name="js/courseware/course_info_events" class_name="CourseInfoEvents">
CourseInfoEvents();
</%static:require_module_async>
<%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}</%block>
<main id="main" aria-label="Content" tabindex="-1">
<div class="container"
% if getattr(course, 'language'):
lang="${course.language}"
% endif
>
<div class="home">
<div class="page-header-main">
<h2 class="hd hd-2 page-title">
% 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:
<div class="page-subtitle">${course_subtitle}</div>
% endif
</h2>
</div>
% if resume_course_url and user_is_enrolled:
<div class="page-header-secondary">
<a href="${resume_course_url}" class="last-accessed-link">${_("Resume Course")}</a>
</div>
% endif
</div>
<div class="info-wrapper">
% if user.is_authenticated:
<section class="updates">
% if studio_url is not None and masquerade and masquerade.role == 'staff':
<div class="wrap-instructor-info studio-view">
<a class="instructor-info-action" href="${studio_url}">
${_("View Updates in Studio")}
</a>
</div>
% endif
<h3 class="hd hd-3">${_("Course Updates and News")}</h3>
${HTML(get_course_info_section(request, masquerade_user, course, 'updates'))}
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
% if course_tools:
<h3 class="hd hd-3 handouts-header">${_("Course Tools")}</h3>
% for course_tool in course_tools:
<a href="${course_tool.url(course.id)}">
<span class="icon ${course_tool.icon_classes()}" aria-hidden="true"></span>
${course_tool.title()}
</a>
% endfor
% endif
% if SelfPacedConfiguration.current().enable_course_home_improvements:
${HTML(dates_fragment.body_html())}
% endif
<h3 class="hd hd-3 handouts-header">${_(course.info_sidebar_name)}</h3>
${HTML(get_course_info_section(request, masquerade_user, course, 'handouts'))}
</section>
% else:
<section class="updates">
<h3 class="hd hd-3 handouts-header">${_("Course Updates and News")}</h3>
${HTML(get_course_info_section(request, masquerade_user, course, 'guest_updates'))}
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
<h3 class="hd hd-3 handouts-header">${_("Course Handouts")}</h3>
${HTML(get_course_info_section(request, masquerade_user, course, 'guest_handouts'))}
</section>
% endif <!-- if course authenticated -->
</div>
</div>
</main>
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform(iterationKey=".localized-datetime");
</%static:require_module_async>

View File

@@ -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)),

View File

@@ -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']

View File

@@ -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
``<studio_url>/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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
},
),
]

View File

@@ -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.")
)

View File

@@ -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):

View File

@@ -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')

View File

@@ -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)

View File

@@ -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'],

View File

@@ -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",