diff --git a/lms/djangoapps/learner_dashboard/config/waffle.py b/lms/djangoapps/learner_dashboard/config/waffle.py
index 0809383a48..32922974b3 100644
--- a/lms/djangoapps/learner_dashboard/config/waffle.py
+++ b/lms/djangoapps/learner_dashboard/config/waffle.py
@@ -5,16 +5,35 @@ waffle switches for the learner_dashboard app.
from edx_toggles.toggles import WaffleFlag
-# .. toggle_name: learner_dashboard.enable_program_discussions
+# .. toggle_name: learner_dashboard.enable_program_tab_view
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
-# .. toggle_description: Waffle flag to enable new Program discussion experience for course.
+# .. toggle_description: Waffle flag to enable new Program discussion experience in tab view for course.
+# This flag is used to decide weather we need to render program data in "tab" view or simple view.
+# In the new tab view, we have tabs like "journey", "live", "discussions"
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2021-08-25
# .. toggle_target_removal_date: 2021-12-31
# .. toggle_warnings: When the flag is ON, the new experience for Program discussions will be enabled.
# .. toggle_tickets: TNL-8434
-ENABLE_PROGRAM_DISCUSSIONS = WaffleFlag(
- 'learner_dashboard.enable_program_discussions',
+ENABLE_PROGRAM_TAB_VIEW = WaffleFlag(
+ 'learner_dashboard.enable_program_tab_view',
+ __name__,
+)
+
+
+# .. toggle_name: learner_dashboard.enable_masters_program_tab_view
+# .. toggle_implementation: WaffleFlag
+# .. toggle_default: False
+# .. toggle_description: Waffle flag to enable new Masters Program discussion experience for masters program.
+# This flag is used to decide weather we need to render master program data in "tab" view or simple view.
+# In the new tab view, we have tabs like "journey", "live", "discussions"
+# .. toggle_use_cases: temporary, open_edx
+# .. toggle_creation_date: 2021-10-19
+# .. toggle_target_removal_date: 2021-12-31
+# .. toggle_warnings: When the flag is ON, the new tabbed experience for Master Program Page will be enabled.
+# .. toggle_tickets: TNL-8434
+ENABLE_MASTERS_PROGRAM_TAB_VIEW = WaffleFlag(
+ 'learner_dashboard.enable_masters_program_tab_view',
__name__,
)
diff --git a/lms/djangoapps/learner_dashboard/permissions.py b/lms/djangoapps/learner_dashboard/permissions.py
new file mode 100644
index 0000000000..44943b24dd
--- /dev/null
+++ b/lms/djangoapps/learner_dashboard/permissions.py
@@ -0,0 +1,32 @@
+"""
+Permissions for program discussion api
+"""
+from django.core.exceptions import ObjectDoesNotExist
+from rest_framework import permissions, status
+from rest_framework.exceptions import APIException
+
+from lms.djangoapps.program_enrollments.api import get_program_enrollment
+
+
+class IsEnrolledInProgram(permissions.BasePermission):
+ """Permission that checks to see if the user is enrolled in the course or is staff."""
+ def has_permission(self, request, view):
+
+ """Returns true if the user is enrolled in program"""
+ if not view.program:
+ raise ProgramNotFound
+
+ try:
+ get_program_enrollment(program_uuid=view.kwargs.get('program_uuid'), user=request.user)
+ except ObjectDoesNotExist:
+ return False
+ return True
+
+
+class ProgramNotFound(APIException):
+ """
+ custom exception class for Program not found error
+ """
+ status_code = status.HTTP_404_NOT_FOUND
+ default_detail = 'Program not found for provided uuid'
+ default_code = 'program_not_found'
diff --git a/lms/djangoapps/learner_dashboard/programs.py b/lms/djangoapps/learner_dashboard/programs.py
index 88a68b23d4..b3dc1cb5bd 100644
--- a/lms/djangoapps/learner_dashboard/programs.py
+++ b/lms/djangoapps/learner_dashboard/programs.py
@@ -2,7 +2,6 @@
Fragments for rendering programs.
"""
-
import json
from django.contrib.sites.shortcuts import get_current_site
@@ -10,12 +9,13 @@ from django.http import Http404
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.translation import gettext_lazy as _ # lint-amnesty, pylint: disable=unused-import
+from lti_consumer.lti_1p1.contrib.django import lti_embed
from web_fragments.fragment import Fragment
from common.djangoapps.student.roles import GlobalStaff
from lms.djangoapps.commerce.utils import EcommerceService
-from lms.djangoapps.learner_dashboard.utils import FAKE_COURSE_KEY, strip_course_id, program_discussions_is_enabled
-from lti_consumer.lti_1p1.contrib.django import lti_embed
+from lms.djangoapps.learner_dashboard.utils import FAKE_COURSE_KEY, program_tab_view_is_enabled, strip_course_id
+
from openedx.core.djangoapps.catalog.constants import PathwayType
from openedx.core.djangoapps.catalog.utils import get_pathways
from openedx.core.djangoapps.credentials.utils import get_credentials_records_url
@@ -75,93 +75,12 @@ class ProgramDetailsFragmentView(EdxFragmentView):
"""
Render the program details fragment.
"""
- DEFAULT_ROLE = 'Student'
- ADMIN_ROLE = 'Administrator'
-
- @staticmethod
- def get_program_discussion_configuration(program_uuid):
- return ProgramDiscussionsConfiguration.objects.filter(
- program_uuid=program_uuid
- ).first()
@staticmethod
def _get_resource_link_id(program_uuid, request) -> str:
site = get_current_site(request)
return f'{site.domain}-{program_uuid}'
- @staticmethod
- def _get_result_sourcedid(context_id, resource_link_id, user_id) -> str:
- return f'{context_id}:{resource_link_id}:{user_id}'
-
- def get_user_roles(self, user):
- """
- Returns the given user's roles
- """
- if GlobalStaff().has_user(user):
- return self.ADMIN_ROLE
- return self.DEFAULT_ROLE
-
- def _get_lti_embed_code(self, program_discussions_configuration, request) -> str:
- """
- Returns the LTI embed code for embedding in the program discussions tab
- Args:
- program_discussions_configuration (ProgramDiscussionsConfiguration): ProgramDiscussionsConfiguration object.
- request (HttpRequest): Request object for view in which LTI will be embedded.
- Returns:
- HTML code to embed LTI in program page.
- """
- program_uuid = program_discussions_configuration.program_uuid
- lti_consumer = program_discussions_configuration.lti_configuration.get_lti_consumer()
- user_id = str(request.user.id)
- context_id = program_uuid
- resource_link_id = self._get_resource_link_id(program_uuid, request)
- roles = self.get_user_roles(request.user)
- context_title = program_uuid
- result_sourcedid = self._get_result_sourcedid(context_id, resource_link_id, user_id)
-
- return lti_embed(
- html_element_id='lti-tab-launcher',
- lti_consumer=lti_consumer,
- resource_link_id=resource_link_id,
- user_id=user_id,
- roles=roles,
- context_id=context_id,
- context_title=context_title,
- context_label=context_id,
- result_sourcedid=result_sourcedid
- )
-
- def render_discussions_fragment(self, program_uuid, request) -> dict:
- """
- Returns the program discussion fragment if program discussions configuration exists for a program uuid
- """
- if program_discussions_is_enabled():
- program_discussions_configuration = self.get_program_discussion_configuration(program_uuid)
- if program_discussions_configuration:
- lti_embed_html = self._get_lti_embed_code(program_discussions_configuration, request)
- fragment = Fragment(
- HTML(
- """
-
- """
- ).format(
- srcdoc=lti_embed_html
- )
- )
- return {
- 'iframe': fragment.content,
- 'enabled': True
- }
- return {
- 'iframe': '',
- 'enabled': False
- }
-
def render_to_fragment(self, request, program_uuid, **kwargs): # lint-amnesty, pylint: disable=arguments-differ
"""View details about a specific program."""
programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current()
@@ -219,7 +138,7 @@ class ProgramDetailsFragmentView(EdxFragmentView):
'buy_button_url': ecommerce_service.get_checkout_page_url(*skus),
'program_record_url': program_record_url,
}
-
+ program_discussion_lti = ProgramDiscussionLTI(program_uuid, request)
context = {
'urls': urls,
'user_preferences': get_user_preferences(user),
@@ -228,8 +147,11 @@ class ProgramDetailsFragmentView(EdxFragmentView):
'certificate_data': certificate_data,
'industry_pathways': industry_pathways,
'credit_pathways': credit_pathways,
- 'program_discussions_enabled': program_discussions_is_enabled(),
- 'discussion_fragment': self.render_discussions_fragment(program_uuid, request)
+ 'program_discussions_enabled': program_tab_view_is_enabled(),
+ 'discussion_fragment': {
+ 'enabled': bool(program_discussion_lti.configuration),
+ 'iframe': program_discussion_lti.render_iframe()
+ }
}
html = render_to_string('learner_dashboard/program_details_fragment.html', context)
@@ -242,3 +164,88 @@ class ProgramDetailsFragmentView(EdxFragmentView):
Return page title for the standalone page.
"""
return _('Program Details')
+
+
+class ProgramDiscussionLTI:
+ """
+ Encapsulates methods for program discussion iframe rendering.
+ """
+ DEFAULT_ROLE = 'Student'
+ ADMIN_ROLE = 'Administrator'
+
+ def __init__(self, program_uuid, request):
+ self.program_uuid = program_uuid
+ self.request = request
+ self.configuration = self.get_configuration()
+
+ def get_configuration(self) -> ProgramDiscussionsConfiguration:
+ """
+ Returns ProgramDiscussionsConfiguration object with respect to program_uuid
+ """
+ return ProgramDiscussionsConfiguration.objects.filter(
+ program_uuid=self.program_uuid
+ ).first()
+
+ def _get_resource_link_id(self) -> str:
+ site = get_current_site(self.request)
+ return f'{site.domain}-{self.program_uuid}'
+
+ def _get_result_sourcedid(self, resource_link_id) -> str:
+ return f'{self.program_uuid}:{resource_link_id}:{self.request.user.id}'
+
+ def get_user_roles(self) -> str:
+ """
+ Returns comma-separated roles for the given user
+ """
+ basic_role = self.DEFAULT_ROLE
+
+ if GlobalStaff().has_user(self.request.user):
+ basic_role = self.ADMIN_ROLE
+
+ all_roles = [basic_role]
+ return ','.join(all_roles)
+
+ def _get_lti_embed_code(self) -> str:
+ """
+ Returns the LTI embed code for embedding in the program discussions tab
+ Returns:
+ HTML code to embed LTI in program page.
+ """
+ resource_link_id = self._get_resource_link_id()
+ result_sourcedid = self._get_result_sourcedid(resource_link_id)
+
+ return lti_embed(
+ html_element_id='lti-tab-launcher',
+ lti_consumer=self.configuration.lti_configuration.get_lti_consumer(),
+ resource_link_id=resource_link_id,
+ user_id=str(self.request.user.id),
+ roles=self.get_user_roles(),
+ context_id=self.program_uuid,
+ context_title=self.program_uuid,
+ context_label=self.program_uuid,
+ result_sourcedid=result_sourcedid
+ )
+
+ def render_iframe(self) -> str:
+ """
+ Returns the program discussion fragment if program discussions configuration exists for a program uuid
+ """
+ if not self.configuration:
+ return ''
+
+ lti_embed_html = self._get_lti_embed_code()
+ fragment = Fragment(
+ HTML(
+ """
+
+ """
+ ).format(
+ srcdoc=lti_embed_html
+ )
+ )
+ return fragment.content
diff --git a/lms/djangoapps/learner_dashboard/tests/test_programs.py b/lms/djangoapps/learner_dashboard/tests/test_programs.py
index f02e1a21bb..af75fb48c5 100644
--- a/lms/djangoapps/learner_dashboard/tests/test_programs.py
+++ b/lms/djangoapps/learner_dashboard/tests/test_programs.py
@@ -13,8 +13,13 @@ from bs4 import BeautifulSoup
from django.conf import settings
from django.test import override_settings
from django.urls import reverse, reverse_lazy
+from edx_toggles.toggles.testutils import override_waffle_flag
+from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
+from lms.djangoapps.learner_dashboard.config.waffle import ENABLE_PROGRAM_TAB_VIEW
+from lms.djangoapps.program_enrollments.rest_api.v1.tests.test_views import ProgramCacheMixin
from lms.envs.test import CREDENTIALS_PUBLIC_SERVICE_URL
from openedx.core.djangoapps.catalog.constants import PathwayType
from openedx.core.djangoapps.catalog.tests.factories import (
@@ -26,8 +31,6 @@ from openedx.core.djangoapps.catalog.tests.factories import (
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangolib.testing.utils import skip_unless_lms
-from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory
PROGRAMS_UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
PROGRAMS_MODULE = 'lms.djangoapps.learner_dashboard.programs'
@@ -298,3 +301,35 @@ class TestProgramDetails(ProgramsApiConfigMixin, CatalogIntegrationMixin, Shared
response = self.client.get(self.url)
assert response.status_code == 404
+
+
+@override_waffle_flag(ENABLE_PROGRAM_TAB_VIEW, active=True)
+class TestProgramDetailsFragmentView(SharedModuleStoreTestCase, ProgramCacheMixin, ProgramsApiConfigMixin):
+ """Unit tests for the program details page."""
+ program_uuid = str(uuid4())
+ password = 'test'
+ url = reverse_lazy('program_details_fragment_view', kwargs={'program_uuid': program_uuid})
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ modulestore_course = ModuleStoreCourseFactory()
+ course_run = CourseRunFactory(key=str(modulestore_course.id))
+ course = CourseFactory(course_runs=[course_run])
+ cls.program = ProgramFactory(uuid=cls.program_uuid, courses=[course])
+
+ def setUp(self):
+ super().setUp()
+ self.user = UserFactory()
+ self.client.login(username=self.user.username, password=self.password)
+ self.set_program_in_catalog_cache(self.program_uuid, self.program)
+ self.create_programs_config()
+
+ def test_discussion_flags_exist(self):
+ """
+ Test if programDiscussionEnabled and discussionFragment exist in html.
+ """
+ response = self.client.get(self.url)
+ self.assertContains(response, 'programDiscussionEnabled: true',)
+ self.assertContains(response, 'discussionFragment: {"enabled": false, "iframe": ""')
diff --git a/lms/djangoapps/learner_dashboard/tests/test_views.py b/lms/djangoapps/learner_dashboard/tests/test_views.py
new file mode 100644
index 0000000000..b52febea21
--- /dev/null
+++ b/lms/djangoapps/learner_dashboard/tests/test_views.py
@@ -0,0 +1,108 @@
+"""
+Unit tests covering the program discussion iframe API.
+"""
+
+from uuid import uuid4
+
+from django.urls import reverse, reverse_lazy
+from edx_toggles.toggles.testutils import override_waffle_flag
+from lti_consumer.models import LtiConfiguration
+from markupsafe import Markup
+from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory
+
+from common.djangoapps.student.tests.factories import UserFactory
+from lms.djangoapps.learner_dashboard.config.waffle import ENABLE_PROGRAM_TAB_VIEW, ENABLE_MASTERS_PROGRAM_TAB_VIEW
+from lms.djangoapps.program_enrollments.rest_api.v1.tests.test_views import ProgramCacheMixin
+from lms.djangoapps.program_enrollments.tests.factories import ProgramEnrollmentFactory
+from openedx.core.djangoapps.catalog.tests.factories import CourseFactory, CourseRunFactory, ProgramFactory
+from openedx.core.djangoapps.discussions.models import ProgramDiscussionsConfiguration
+
+
+@override_waffle_flag(ENABLE_PROGRAM_TAB_VIEW, active=True)
+@override_waffle_flag(ENABLE_MASTERS_PROGRAM_TAB_VIEW, active=True)
+class TestProgramDiscussionIframeView(SharedModuleStoreTestCase, ProgramCacheMixin):
+ """Unit tests for the program details page."""
+ program_uuid = str(uuid4())
+ password = 'test'
+ url = reverse_lazy('program_discussion', kwargs={'program_uuid': program_uuid})
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ modulestore_course = ModuleStoreCourseFactory()
+ course_run = CourseRunFactory(key=str(modulestore_course.id))
+ course = CourseFactory(course_runs=[course_run])
+ cls.program = ProgramFactory(uuid=cls.program_uuid, courses=[course])
+
+ def setUp(self):
+ super().setUp()
+ self.user = UserFactory()
+ self.client.login(username=self.user.username, password=self.password)
+ self.set_program_in_catalog_cache(self.program_uuid, self.program)
+ ProgramEnrollmentFactory.create(
+ user=self.user,
+ program_uuid=self.program_uuid,
+ external_user_key='0001',
+ )
+
+ def test_program_discussion_not_configured(self):
+ """
+ Verify API returns proper response in case ProgramDiscussions is not Configured.
+ """
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, 200)
+ expected_data = {
+ 'enabled': True,
+ 'discussion': {
+ 'iframe': "",
+ 'configured': False
+ }
+ }
+ self.assertEqual(response.data, expected_data)
+
+ def test_if_user_is_not_authenticated(self):
+ """
+ Verify that 401 is returned if user is not authenticated.
+ """
+ self.client.logout()
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, 401)
+
+ def test_api_returns_discussions_iframe(self):
+ """
+ Test if API returns iframe in case ProgramDiscussionsConfiguration model contains proper data
+ """
+ discussion_config = ProgramDiscussionsConfiguration.objects.create(
+ program_uuid=self.program_uuid,
+ enabled=True,
+ provider_type="piazza",
+ )
+ discussion_config.lti_configuration = LtiConfiguration.objects.create(
+ config_store=LtiConfiguration.CONFIG_ON_DB,
+ lti_1p1_launch_url='http://test.url',
+ lti_1p1_client_key='test_client_key',
+ lti_1p1_client_secret='test_client_secret',
+ )
+ discussion_config.save()
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIsInstance(response.data['discussion']['iframe'], Markup)
+ self.assertIn('iframe', str(response.data['discussion']['iframe']), )
+
+ def test_program_does_not_exist(self):
+ """
+ Test if API returns 404 in case program does not exist
+ """
+ response = self.client.get(reverse('program_discussion', kwargs={'program_uuid': str(uuid4())}))
+ self.assertEqual(response.status_code, 404)
+
+ def test_program_access_denied(self):
+ """
+ Test if API returns 403 in case user has no access to program
+ """
+ self.user = UserFactory()
+ self.client.login(username=self.user.username, password=self.password)
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, 403)
diff --git a/lms/djangoapps/learner_dashboard/urls.py b/lms/djangoapps/learner_dashboard/urls.py
index 3b1f297e2c..a7274a9c90 100644
--- a/lms/djangoapps/learner_dashboard/urls.py
+++ b/lms/djangoapps/learner_dashboard/urls.py
@@ -1,6 +1,5 @@
"""Learner dashboard URL routing configuration"""
-
from django.urls import path, re_path
from lms.djangoapps.learner_dashboard import programs, views
@@ -8,6 +7,8 @@ from lms.djangoapps.learner_dashboard import programs, views
urlpatterns = [
path('programs/', views.program_listing, name='program_listing_view'),
re_path(r'^programs/(?P[0-9a-f-]+)/$', views.program_details, name='program_details_view'),
+ re_path(r'^programs/(?P[0-9a-f-]+)/discussion/$', views.ProgramDiscussionIframeView.as_view(),
+ name='program_discussion'),
path('programs_fragment/', programs.ProgramsFragmentView.as_view(), name='program_listing_fragment_view'),
re_path(r'^programs/(?P[0-9a-f-]+)/details_fragment/$', programs.ProgramDetailsFragmentView.as_view(),
name='program_details_fragment_view'),
diff --git a/lms/djangoapps/learner_dashboard/utils.py b/lms/djangoapps/learner_dashboard/utils.py
index 2b0385a9c6..89c35f7e2d 100644
--- a/lms/djangoapps/learner_dashboard/utils.py
+++ b/lms/djangoapps/learner_dashboard/utils.py
@@ -2,10 +2,9 @@
The utility methods and functions to help the djangoapp logic
"""
-
from opaque_keys.edx.keys import CourseKey
-from lms.djangoapps.learner_dashboard.config.waffle import ENABLE_PROGRAM_DISCUSSIONS
+from lms.djangoapps.learner_dashboard.config.waffle import ENABLE_PROGRAM_TAB_VIEW, ENABLE_MASTERS_PROGRAM_TAB_VIEW
FAKE_COURSE_KEY = CourseKey.from_string('course-v1:fake+course+run')
@@ -19,8 +18,15 @@ def strip_course_id(path):
return path.split(course_id)[0]
-def program_discussions_is_enabled():
+def program_tab_view_is_enabled() -> bool:
"""
check if program discussion is enabled.
"""
- return ENABLE_PROGRAM_DISCUSSIONS.is_enabled()
+ return ENABLE_PROGRAM_TAB_VIEW.is_enabled()
+
+
+def masters_program_tab_view_is_enabled() -> bool:
+ """
+ check if masters program discussion is enabled.
+ """
+ return ENABLE_MASTERS_PROGRAM_TAB_VIEW.is_enabled()
diff --git a/lms/djangoapps/learner_dashboard/views.py b/lms/djangoapps/learner_dashboard/views.py
index db6c6724e8..80e6710385 100644
--- a/lms/djangoapps/learner_dashboard/views.py
+++ b/lms/djangoapps/learner_dashboard/views.py
@@ -1,12 +1,22 @@
"""Learner dashboard views"""
-
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_GET
-
+from rest_framework import permissions, status
+from rest_framework.authentication import SessionAuthentication
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from lms.djangoapps.learner_dashboard.utils import masters_program_tab_view_is_enabled
from common.djangoapps.edxmako.shortcuts import render_to_response
-from lms.djangoapps.learner_dashboard.programs import ProgramDetailsFragmentView, ProgramsFragmentView
+from lms.djangoapps.learner_dashboard.permissions import IsEnrolledInProgram
+from lms.djangoapps.learner_dashboard.programs import (
+ ProgramDetailsFragmentView,
+ ProgramDiscussionLTI,
+ ProgramsFragmentView
+)
+from lms.djangoapps.program_enrollments.rest_api.v1.utils import ProgramSpecificViewMixin
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
+from openedx.core.lib.api.authentication import BearerAuthentication
@login_required
@@ -47,3 +57,65 @@ def program_details(request, program_uuid):
}
return render_to_response('learner_dashboard/program_details.html', context)
+
+
+class ProgramDiscussionIframeView(APIView, ProgramSpecificViewMixin):
+ """
+ A view for retrieving Program Discussion IFrame .
+
+ Path: ``/dashboard/programs/{program_uuid}/discussion/``
+
+ Accepts: [GET]
+
+ ------------------------------------------------------------------------------------
+ GET
+ ------------------------------------------------------------------------------------
+
+ **Returns**
+
+ * 200: OK - Contains a program discussion iframe.
+ * 401: The requesting user is not authenticated.
+ * 403: The requesting user lacks access to the program.
+ * 404: The requested program does not exist.
+
+ **Response**
+
+ In the case of a 200 response code, the response will be iframe HTML and status if discussion is configured
+ for the program.
+
+ **Example**
+
+ {
+ 'enabled_for_masters': True,
+ 'discussion': {
+ "iframe": "
+
+ ",
+ "enabled": false
+ }
+ }
+
+
+ """
+ authentication_classes = (BearerAuthentication, SessionAuthentication)
+ permission_classes = (permissions.IsAuthenticated, IsEnrolledInProgram)
+
+ def get(self, request, program_uuid):
+ """ GET handler """
+ program_discussion_lti = ProgramDiscussionLTI(program_uuid, request)
+ return Response(
+
+ {
+ 'enabled': masters_program_tab_view_is_enabled(),
+ 'discussion': {
+ 'iframe': program_discussion_lti.render_iframe(),
+ 'configured': bool(program_discussion_lti.configuration),
+ }
+ },
+ status=status.HTTP_200_OK
+ )