AA-253: don't send outline if not enrolled

This is for the learning MFE outline endpoint.

Also clean up some related permissions issues.
This commit is contained in:
Michael Terry
2020-09-25 15:44:48 -04:00
parent 8e1ea3e536
commit 100018081d
4 changed files with 85 additions and 62 deletions

View File

@@ -2,12 +2,13 @@
Tests for Outline Tab API in the Course Home API
"""
import itertools
from datetime import datetime
import ddt
from django.conf import settings
from django.urls import reverse
from mock import patch
from mock import Mock, patch
from course_modes.models import CourseMode
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
@@ -17,7 +18,7 @@ from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactor
from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, ENABLE_COURSE_GOALS
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -108,29 +109,11 @@ class OutlineTabTestViews(BaseCourseHomeTests):
@COURSE_HOME_MICROFRONTEND.override(active=True)
@COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True)
@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)
def test_handouts(self):
CourseEnrollment.enroll(self.user, self.course.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 '')
self.assertEqual(self.client.get(self.url).data['handouts_html'], '<p>Hi</p>')
@COURSE_HOME_MICROFRONTEND.override(active=True)
@COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True)
@@ -151,6 +134,7 @@ class OutlineTabTestViews(BaseCourseHomeTests):
@COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True)
@ddt.data(True, False)
def test_welcome_message(self, welcome_message_is_dismissed):
CourseEnrollment.enroll(self.user, self.course.id)
self.store.create_item(
self.user.id, self.course.id,
'course_info',
@@ -175,19 +159,17 @@ class OutlineTabTestViews(BaseCourseHomeTests):
@COURSE_HOME_MICROFRONTEND.override(active=True)
@COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True)
@patch('lms.djangoapps.course_home_api.outline.v1.views.generate_offer_html', new=Mock(return_value='<p>Offer</p>'))
def test_offer_html(self):
with patch('lms.djangoapps.course_home_api.outline.v1.views.generate_offer_html') as gen_html:
html = '<div>Offer HTML</div>'
gen_html.return_value = html
self.assertEqual(self.client.get(self.url).data['offer_html'], html)
CourseEnrollment.enroll(self.user, self.course.id)
self.assertEqual(self.client.get(self.url).data['offer_html'], '<p>Offer</p>')
@COURSE_HOME_MICROFRONTEND.override(active=True)
@COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override(active=True)
@patch('lms.djangoapps.course_home_api.outline.v1.views.generate_course_expired_message', new=Mock(return_value='<p>Expired</p>'))
def test_course_expired_html(self):
with patch('lms.djangoapps.course_home_api.outline.v1.views.generate_course_expired_message') as gen_html:
html = '<div>Course expired HTML</div>'
gen_html.return_value = html
self.assertEqual(self.client.get(self.url).data['course_expired_html'], html)
CourseEnrollment.enroll(self.user, self.course.id)
self.assertEqual(self.client.get(self.url).data['course_expired_html'], '<p>Expired</p>')
@ENABLE_COURSE_GOALS.override(active=True)
@COURSE_HOME_MICROFRONTEND.override(active=True)
@@ -284,3 +266,42 @@ class OutlineTabTestViews(BaseCourseHomeTests):
ungraded_data = response.data['course_blocks']['blocks'][str(sequential2.location)]
self.assertEqual(ungraded_data['display_name'], 'Ungraded')
self.assertIsNone(ungraded_data['icon'])
@COURSE_HOME_MICROFRONTEND.override()
@COURSE_HOME_MICROFRONTEND_OUTLINE_TAB.override()
@COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.override()
@patch('lms.djangoapps.course_home_api.outline.v1.views.generate_offer_html', new=Mock(return_value='<p>Offer</p>'))
@patch('lms.djangoapps.course_home_api.outline.v1.views.generate_course_expired_message', new=Mock(return_value='<p>Expired</p>'))
@ddt.data(*itertools.product([True, False], [True, False],
[None, COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE]))
@ddt.unpack
def test_visibility(self, is_enrolled, is_staff, course_visibility):
if is_enrolled:
CourseEnrollment.enroll(self.user, self.course.id)
if is_staff:
self.user.is_staff = True
self.user.save()
if course_visibility:
self.course.course_visibility = course_visibility
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>Handouts</p>'})
self.store.create_item(self.user.id, self.course.id, 'course_info', 'updates', fields={
'items': [{
'content': '<p>Welcome</p>',
'status': 'visible',
'date': 'July 23, 2020',
'id': 1,
}]
})
show_enrolled = is_enrolled or is_staff
is_public = course_visibility == COURSE_VISIBILITY_PUBLIC
is_public_outline = course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE
data = self.client.get(self.url).data
self.assertEqual(data['course_blocks'] is not None, show_enrolled or is_public or is_public_outline)
self.assertEqual(data['handouts_html'] is not None, show_enrolled or is_public)
self.assertEqual(data['offer_html'] is not None, show_enrolled)
self.assertEqual(data['course_expired_html'] is not None, show_enrolled)
self.assertEqual(data['resume_course']['url'] is not None, show_enrolled)

View File

@@ -39,7 +39,7 @@ from openedx.features.course_experience.views.latest_update import LatestUpdateF
from openedx.features.course_experience.views.welcome_message import PREFERENCE_KEY, WelcomeMessageFragmentView
from openedx.features.discounts.utils import generate_offer_html
from student.models import CourseEnrollment
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
from xmodule.modulestore.django import modulestore
@@ -150,22 +150,23 @@ class OutlineTabView(RetrieveAPIView):
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
allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE
is_enrolled = enrollment and enrollment.is_active
is_staff = has_access(request.user, 'staff', course_key)
is_staff = bool(has_access(request.user, 'staff', course_key))
show_enrolled = is_enrolled or is_staff
show_handouts = show_enrolled or allow_public
handouts_html = get_course_info_section(request, request.user, course, 'handouts') if show_handouts else ''
# TODO: TNL-7185 Legacy: Refactor to return the offer & expired data and format the message in the MFE
offer_html = generate_offer_html(request.user, course_overview)
course_expired_html = generate_course_expired_message(request.user, course_overview)
offer_html = show_enrolled and generate_offer_html(request.user, course_overview)
course_expired_html = show_enrolled and generate_course_expired_message(request.user, course_overview)
welcome_message_html = None
if get_course_tag(request.user, course_key, PREFERENCE_KEY) != 'False':
if show_enrolled:
if LATEST_UPDATE_FLAG.is_enabled(course_key):
welcome_message_html = LatestUpdateFragmentView().latest_update_html(request, course)
else:
elif get_course_tag(request.user, course_key, PREFERENCE_KEY) != 'False':
welcome_message_html = WelcomeMessageFragmentView().welcome_message_html(request, course)
enroll_alert = {
@@ -191,25 +192,26 @@ class OutlineTabView(RetrieveAPIView):
if course_home_mfe_dates_tab_is_active(course.id):
dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates')
course_blocks = get_course_outline_block_tree(request, course_key_string, request.user if is_enrolled else None)
has_visited_course = False
try:
resume_block = get_key_to_last_completed_block(request.user, course.id)
has_visited_course = True
except UnavailableCompletionData:
resume_block = course_usage_key
resume_path = reverse('jump_to', kwargs={
'course_id': course_key_string,
'location': str(resume_block)
})
resume_course_url = request.build_absolute_uri(resume_path)
course_blocks = None
if show_enrolled or allow_public or allow_public_outline:
outline_user = request.user if show_enrolled else None
course_blocks = get_course_outline_block_tree(request, course_key_string, outline_user)
resume_course = {
'has_visited_course': has_visited_course,
'url': resume_course_url,
'has_visited_course': False,
'url': None,
}
if show_enrolled:
try:
resume_block = get_key_to_last_completed_block(request.user, course.id)
resume_course['has_visited_course'] = True
except UnavailableCompletionData:
resume_block = course_usage_key
resume_path = reverse('jump_to', kwargs={
'course_id': course_key_string,
'location': str(resume_block)
})
resume_course['url'] = request.build_absolute_uri(resume_path)
dates_widget = {
'course_date_blocks': [block for block in date_blocks if not isinstance(block, TodaysDate)],
@@ -241,15 +243,15 @@ class OutlineTabView(RetrieveAPIView):
data = {
'course_blocks': course_blocks,
'course_expired_html': course_expired_html,
'course_expired_html': course_expired_html or None,
'course_goals': course_goals,
'course_tools': course_tools,
'dates_widget': dates_widget,
'enroll_alert': enroll_alert,
'handouts_html': handouts_html,
'offer_html': offer_html,
'handouts_html': handouts_html or None,
'offer_html': offer_html or None,
'resume_course': resume_course,
'welcome_message_html': welcome_message_html,
'welcome_message_html': welcome_message_html or None,
}
context = self.get_serializer_context()
context['course_key'] = course_key

View File

@@ -42,16 +42,15 @@ def can_show_verified_upgrade(user, enrollment, course=None):
Arguments:
user (:class:`.AuthUser`): The user from the request.user property
enrollment (:class:`.CourseEnrollment`): The enrollment under consideration.
If None, then the enrollment is considered to be upgradeable.
If None, then the enrollment is not considered to be upgradeable.
course (:class:`.ModulestoreCourse`): Optional passed in modulestore course.
If provided, it is expected to correspond to `enrollment.course.id`.
If not provided, the course will be loaded from the modulestore.
We use the course to retrieve user partitions when calculating whether
the upgrade link will be shown.
"""
# Return `true` if user is not enrolled in course
if enrollment is None:
return False
return False # this got accidentally flipped in 2017 (commit 8468357), but leaving alone to not switch again
partition_service = PartitionService(enrollment.course.id, course=course)
enrollment_track_partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID)
group = partition_service.get_group(user, enrollment_track_partition)

View File

@@ -34,6 +34,7 @@ from lms.djangoapps.courseware.toggles import REDIRECT_TO_COURSEWARE_MICROFRONTE
from lms.djangoapps.courseware.utils import can_show_verified_upgrade
from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.access import generate_course_expired_message
from openedx.features.discounts.utils import generate_offer_html
@@ -132,8 +133,8 @@ class CoursewareMeta:
@property
def can_show_upgrade_sock(self):
can_show = can_show_verified_upgrade(self.effective_user, self.enrollment_object)
return can_show
return (DISPLAY_COURSE_SOCK_FLAG.is_enabled(self.course_key) and
can_show_verified_upgrade(self.effective_user, self.enrollment_object))
@property
def license(self):