diff --git a/lms/djangoapps/course_home_api/course_metadata/v1/tests/test_views.py b/lms/djangoapps/course_home_api/course_metadata/v1/tests/test_views.py index e2e6f67565..4f6a2d4c6e 100644 --- a/lms/djangoapps/course_home_api/course_metadata/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/course_metadata/v1/tests/test_views.py @@ -18,10 +18,9 @@ class CourseHomeMetadataTests(BaseCourseHomeTests): """ Tests for the Course Home Course Metadata API """ - @classmethod - def setUpClass(cls): - BaseCourseHomeTests.setUpClass() - cls.url = reverse('course-home-course-metadata', args=[cls.course.id]) + def setUp(self): + super().setUp() + self.url = reverse('course-home-course-metadata', args=[self.course.id]) def test_get_authenticated_user(self): CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) diff --git a/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py b/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py index 467a6492d9..4727a399ee 100644 --- a/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py @@ -19,10 +19,9 @@ class DatesTabTestViews(BaseCourseHomeTests): """ Tests for the Dates Tab API """ - @classmethod - def setUpClass(cls): - BaseCourseHomeTests.setUpClass() - cls.url = reverse('course-home-dates-tab', args=[cls.course.id]) + def setUp(self): + super().setUp() + self.url = reverse('course-home-dates-tab', args=[self.course.id]) ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2017, 1, 1)) @COURSE_HOME_MICROFRONTEND.override(active=True) @@ -70,7 +69,7 @@ class DatesTabTestViews(BaseCourseHomeTests): @COURSE_HOME_MICROFRONTEND.override(active=True) @COURSE_HOME_MICROFRONTEND_DATES_TAB.override(active=True) def test_masquerade(self): - self.upgrade_to_staff() + self.switch_to_staff() CourseEnrollment.enroll(self.user, self.course.id, 'audit') self.assertTrue(self.client.get(self.url).data.get('learner_is_full_access')) diff --git a/lms/djangoapps/course_home_api/outline/v1/serializers.py b/lms/djangoapps/course_home_api/outline/v1/serializers.py index ec93c235e7..59e7893d2c 100644 --- a/lms/djangoapps/course_home_api/outline/v1/serializers.py +++ b/lms/djangoapps/course_home_api/outline/v1/serializers.py @@ -58,6 +58,7 @@ class OutlineTabSerializer(serializers.Serializer): """ Serializer for the Outline Tab """ - course_tools = CourseToolSerializer(many=True) course_blocks = CourseBlockSerializer() + course_tools = CourseToolSerializer(many=True) dates_widget = DatesWidgetSerializer() + handouts_html = serializers.CharField() diff --git a/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py b/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py index 5b479b177c..c4b97484f5 100644 --- a/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py @@ -8,8 +8,10 @@ from django.urls import reverse from course_modes.models import CourseMode from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests from openedx.core.djangoapps.user_api.preferences.api import set_user_preference +from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG from student.models import CourseEnrollment from student.tests.factories import UserFactory +from xmodule.course_module import COURSE_VISIBILITY_PUBLIC @ddt.ddt @@ -18,10 +20,9 @@ class OutlineTabTestViews(BaseCourseHomeTests): Tests for the Outline Tab API """ - @classmethod - def setUpClass(cls): - BaseCourseHomeTests.setUpClass() - cls.url = reverse('course-home-outline-tab', args=[cls.course.id]) + def setUp(self): + super().setUp() + self.url = reverse('course-home-outline-tab', args=[self.course.id]) @ddt.data(CourseMode.AUDIT, CourseMode.VERIFIED) def test_get_authenticated_enrolled_user(self, enrollment_mode): @@ -62,7 +63,7 @@ class OutlineTabTestViews(BaseCourseHomeTests): set_user_preference(user, 'time_zone', 'Asia/Tokyo') CourseEnrollment.enroll(user, self.course.id) - self.upgrade_to_staff() # needed for masquerade + self.switch_to_staff() # needed for masquerade # Sanity check on our normal user self.assertEqual(self.client.get(self.url).data['dates_widget']['user_timezone'], None) @@ -71,4 +72,28 @@ class OutlineTabTestViews(BaseCourseHomeTests): self.update_masquerade(username=user.username) self.assertEqual(self.client.get(self.url).data['dates_widget']['user_timezone'], 'Asia/Tokyo') + @ddt.data( + (True, True, True, True), # happy path + (True, False, False, True), # is enrolled + (False, True, False, True), # is staff + (False, False, True, True), # public visibility + (False, False, False, False), # no access + ) + @ddt.unpack + @COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.override() + def test_handouts(self, is_enrolled, is_staff, is_public, handouts_visible): + if is_enrolled: + CourseEnrollment.enroll(self.user, self.course.id) + if is_staff: + self.user.is_staff = True + self.user.save() + if is_public: + self.course.course_visibility = COURSE_VISIBILITY_PUBLIC + self.course = self.update_course(self.course, self.user.id) + + self.store.create_item(self.user.id, self.course.id, 'course_info', 'handouts', fields={'data': '

Hi

'}) + + handouts_html = self.client.get(self.url).data['handouts_html'] + self.assertEqual(handouts_html, '

Hi

' if handouts_visible else '') + # TODO: write test_get_unknown_course when more data is pulled into the Outline Tab API diff --git a/lms/djangoapps/course_home_api/outline/v1/views.py b/lms/djangoapps/course_home_api/outline/v1/views.py index 3d2f7b73b3..fe0d9042ae 100644 --- a/lms/djangoapps/course_home_api/outline/v1/views.py +++ b/lms/djangoapps/course_home_api/outline/v1/views.py @@ -11,20 +11,20 @@ from django.urls import reverse from opaque_keys.edx.keys import CourseKey from lms.djangoapps.course_api.blocks.transformers.blocks_api import BlocksAPITransformer +from lms.djangoapps.course_blocks.api import get_course_block_access_transformers, get_course_blocks from lms.djangoapps.course_home_api.outline.v1.serializers import OutlineTabSerializer - from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active +from lms.djangoapps.course_home_api.utils import get_microfrontend_url from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs -from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_with_access +from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_info_section, get_course_with_access from lms.djangoapps.courseware.date_summary import TodaysDate from lms.djangoapps.courseware.masquerade import setup_masquerade -from lms.djangoapps.course_home_api.utils import get_microfrontend_url from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers from openedx.features.course_experience.course_tools import CourseToolsPluginManager - -from lms.djangoapps.course_blocks.api import get_course_blocks -import lms.djangoapps.course_blocks.api as course_blocks_api +from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG +from student.models import CourseEnrollment +from xmodule.course_module import COURSE_VISIBILITY_PUBLIC from xmodule.modulestore.django import modulestore @@ -58,6 +58,7 @@ class OutlineTabView(RetrieveAPIView): xBlock on the web LMS. children: (list) If the block has child blocks, a list of IDs of the child blocks. + handouts_html: (str) Raw HTML for the handouts section of the course info **Returns** @@ -89,6 +90,15 @@ class OutlineTabView(RetrieveAPIView): reset_masquerade_data=True, ) + enrollment = CourseEnrollment.get_enrollment(request.user, course_key) + allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key) + allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC + is_enrolled = enrollment and enrollment.is_active + is_staff = has_access(request.user, 'staff', course_key) + + show_handouts = is_enrolled or is_staff or allow_public + handouts_html = get_course_info_section(request, request.user, course, 'handouts') if show_handouts else '' + course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) @@ -101,7 +111,7 @@ class OutlineTabView(RetrieveAPIView): dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates') transformers = BlockStructureTransformers() - transformers += course_blocks_api.get_course_block_access_transformers(request.user) + transformers += get_course_block_access_transformers(request.user) transformers += [ BlocksAPITransformer(None, None, depth=3), ] @@ -115,9 +125,10 @@ class OutlineTabView(RetrieveAPIView): } data = { - 'course_tools': course_tools, 'course_blocks': course_blocks, + 'course_tools': course_tools, 'dates_widget': dates_widget, + 'handouts_html': handouts_html, } context = self.get_serializer_context() context['course_key'] = course_key diff --git a/lms/djangoapps/course_home_api/tests/utils.py b/lms/djangoapps/course_home_api/tests/utils.py index 09c62d9014..39cdc47c37 100644 --- a/lms/djangoapps/course_home_api/tests/utils.py +++ b/lms/djangoapps/course_home_api/tests/utils.py @@ -14,12 +14,12 @@ from lms.djangoapps.verify_student.models import VerificationDeadline from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from student.tests.factories import UserFactory from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') -class BaseCourseHomeTests(SharedModuleStoreTestCase, MasqueradeMixin): +class BaseCourseHomeTests(ModuleStoreTestCase, MasqueradeMixin): """ Base class for Course Home API tests. @@ -27,46 +27,34 @@ class BaseCourseHomeTests(SharedModuleStoreTestCase, MasqueradeMixin): """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.store = modulestore() - cls.course = CourseFactory.create( + def setUp(self): + super().setUp() + + self.course = CourseFactory.create( start=datetime(2020, 1, 1), end=datetime(2028, 1, 1), enrollment_start=datetime(2020, 1, 1), enrollment_end=datetime(2028, 1, 1), emit_signals=True, - modulestore=cls.store, + modulestore=self.store, ) - chapter = ItemFactory(parent=cls.course, category='chapter') + chapter = ItemFactory(parent=self.course, category='chapter') ItemFactory(parent=chapter, category='sequential') - CourseModeFactory(course_id=cls.course.id, mode_slug=CourseMode.AUDIT) + CourseModeFactory(course_id=self.course.id, mode_slug=CourseMode.AUDIT) CourseModeFactory( - course_id=cls.course.id, + course_id=self.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=datetime(2028, 1, 1) ) - VerificationDeadline.objects.create(course_key=cls.course.id, deadline=datetime(2028, 1, 1)) + VerificationDeadline.objects.create(course_key=self.course.id, deadline=datetime(2028, 1, 1)) - cls.user = UserFactory( - username='student', - email='user@example.com', - password='foo', - is_staff=False - ) CourseOverviewFactory.create(run='1T2020') - @classmethod - def tearDownClass(cls): - super().tearDownClass() - cls.store.delete_course(cls.course.id, cls.user.id) + self.staff_user = self.user + self.user, password = self.create_non_staff_user() + self.client.login(username=self.user.username, password=password) - def setUp(self): - super().setUp() - self.client.login(username=self.user.username, password='foo') - - def upgrade_to_staff(self): - self.user.is_staff = True - self.user.save() + def switch_to_staff(self): + self.user = self.staff_user + self.client.login(username=self.user.username, password=self.user_password)