Merge pull request #24517 from edx/mikix/course-handouts

AA-121: add handouts to course home outlines API
This commit is contained in:
Michael Terry
2020-07-20 08:55:22 -04:00
committed by GitHub
6 changed files with 75 additions and 52 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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