feat: Implement program discussions iframe (#28770)
* feat: Implement program discussions iframe * Fixed conflicts * Fixed conflicts * Fixed conflicts * Refactored code
This commit is contained in:
@@ -5,6 +5,7 @@ Fragments for rendering programs.
|
||||
|
||||
import json
|
||||
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.http import Http404
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
@@ -13,9 +14,11 @@ from web_fragments.fragment import Fragment
|
||||
|
||||
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 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
|
||||
from openedx.core.djangoapps.discussions.models import ProgramDiscussionsConfiguration
|
||||
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.programs.utils import (
|
||||
@@ -25,6 +28,7 @@ from openedx.core.djangoapps.programs.utils import (
|
||||
get_program_marketing_url
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
|
||||
|
||||
class ProgramsFragmentView(EdxFragmentView):
|
||||
@@ -70,14 +74,93 @@ class ProgramDetailsFragmentView(EdxFragmentView):
|
||||
"""
|
||||
Render the program details fragment.
|
||||
"""
|
||||
DEFAULT_ROLE = 'student'
|
||||
|
||||
@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_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)
|
||||
# TODO: Add support for multiple roles
|
||||
roles = self.DEFAULT_ROLE
|
||||
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(
|
||||
"""
|
||||
<iframe
|
||||
id='lti-tab-embed'
|
||||
style='width: 100%; min-height: 800px; border: none'
|
||||
srcdoc='{srcdoc}'
|
||||
>
|
||||
</iframe>
|
||||
"""
|
||||
).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()
|
||||
user = request.user
|
||||
if not programs_config.enabled or not request.user.is_authenticated:
|
||||
raise Http404
|
||||
|
||||
meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid)
|
||||
meter = ProgramProgressMeter(request.site, user, uuid=program_uuid)
|
||||
program_data = meter.programs[0]
|
||||
|
||||
if not program_data:
|
||||
@@ -88,9 +171,9 @@ class ProgramDetailsFragmentView(EdxFragmentView):
|
||||
except ValueError:
|
||||
mobile_only = False
|
||||
|
||||
program_data = ProgramDataExtender(program_data, request.user, mobile_only=mobile_only).extend()
|
||||
program_data = ProgramDataExtender(program_data, user, mobile_only=mobile_only).extend()
|
||||
course_data = meter.progress(programs=[program_data], count_only=False)[0]
|
||||
certificate_data = get_certificates(request.user, program_data)
|
||||
certificate_data = get_certificates(user, program_data)
|
||||
|
||||
program_data.pop('courses')
|
||||
skus = program_data.get('skus')
|
||||
@@ -130,13 +213,14 @@ class ProgramDetailsFragmentView(EdxFragmentView):
|
||||
|
||||
context = {
|
||||
'urls': urls,
|
||||
'user_preferences': get_user_preferences(request.user),
|
||||
'user_preferences': get_user_preferences(user),
|
||||
'program_data': program_data,
|
||||
'course_data': course_data,
|
||||
'certificate_data': certificate_data,
|
||||
'industry_pathways': industry_pathways,
|
||||
'credit_pathways': credit_pathways,
|
||||
'program_discussions_enabled': program_discussions_is_enabled()
|
||||
'program_discussions_enabled': program_discussions_is_enabled(),
|
||||
'discussion_fragment': self.render_discussions_fragment(program_uuid, request)
|
||||
}
|
||||
|
||||
html = render_to_string('learner_dashboard/program_details_fragment.html', context)
|
||||
|
||||
@@ -80,8 +80,9 @@ class ProgramDetailsView extends Backbone.View {
|
||||
completedCount,
|
||||
completeProgramURL: buyButtonUrl,
|
||||
programDiscussionEnabled: this.options.programDiscussionEnabled,
|
||||
industryPathways : this.options.industryPathways,
|
||||
creditPathways : this.options.creditPathways,
|
||||
industryPathways: this.options.industryPathways,
|
||||
creditPathways: this.options.creditPathways,
|
||||
discussionFragment: this.options.discussionFragment,
|
||||
};
|
||||
data = $.extend(data, this.programModel.toJSON());
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
|
||||
@@ -21,6 +21,7 @@ ProgramDetailsFactory({
|
||||
industryPathways: ${industry_pathways | n, dump_js_escaped_json},
|
||||
creditPathways: ${credit_pathways | n, dump_js_escaped_json},
|
||||
programDiscussionEnabled: ${program_discussions_enabled | n, dump_js_escaped_json},
|
||||
discussionFragment: ${discussion_fragment, | n, dump_js_escaped_json}
|
||||
});
|
||||
</%static:webpack>
|
||||
</%block>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link btn-link active" id="home-tab" data-toggle="tab" data-target="#journey" type="button" role="tab" aria-controls="journey" aria-selected="true">Journey</button>
|
||||
</li>
|
||||
<% if (programDiscussionEnabled) { %>
|
||||
<% if (discussionFragment.enabled) { %>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link btn-link" id="profile-tab" data-toggle="tab" data-target="#community" type="button" role="tab" aria-controls="community" aria-selected="false">Community</button>
|
||||
</li>
|
||||
@@ -101,9 +101,9 @@
|
||||
</article>
|
||||
|
||||
</div>
|
||||
<% if (programDiscussionEnabled) { %>
|
||||
<div class="tab-pane fade" id="community" role="tabpanel" aria-labelledby="community-tab">Community tab content</div>
|
||||
<% } %>
|
||||
<% if (discussionFragment.enabled) { %>
|
||||
<div class="tab-pane fade" id="community" role="tabpanel" aria-labelledby="community-tab"><%= HtmlUtils.HTML(discussionFragment.iframe) %></div>
|
||||
<% } %>
|
||||
<div class="tab-pane fade" id="live" role="tabpanel" aria-labelledby="live-tab">Live tab content</div>
|
||||
<div class="tab-pane fade" id="pathways" role="tabpanel" aria-labelledby="pathaways-tab">
|
||||
<% if (creditPathways.length > 0) { %>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<header class="js-program-header program-header full-width-banner"></header>
|
||||
<!-- TODO: consider if article is the most appropriate element here -->
|
||||
|
||||
<article class="program-details-content">
|
||||
<div class="program-heading">
|
||||
<% if (completedCount === totalCount) { %>
|
||||
|
||||
Reference in New Issue
Block a user