Merge pull request #24517 from edx/mikix/course-handouts
AA-121: add handouts to course home outlines API
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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': '<p>Hi</p>'})
|
||||
|
||||
handouts_html = self.client.get(self.url).data['handouts_html']
|
||||
self.assertEqual(handouts_html, '<p>Hi</p>' if handouts_visible else '')
|
||||
|
||||
# TODO: write test_get_unknown_course when more data is pulled into the Outline Tab API
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user