feat: add cta buttons to public video xblock page
* feat: video share page buttons * feat: pass along utm params * feat: disbale register button on public video page * fix: convert to class-based view
This commit is contained in:
@@ -8,7 +8,7 @@ import logging
|
||||
import urllib
|
||||
from collections import OrderedDict, namedtuple
|
||||
from datetime import datetime
|
||||
from urllib.parse import quote_plus, urljoin
|
||||
from urllib.parse import quote_plus, urlencode, urljoin, urlparse, urlunparse
|
||||
|
||||
import bleach
|
||||
import requests
|
||||
@@ -1650,102 +1650,6 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
|
||||
return render_to_response('courseware/courseware-chromeless.html', context)
|
||||
|
||||
|
||||
def _render_public_video_xblock(request, usage_key_string, is_embed=False):
|
||||
"""
|
||||
Look up a given usage key and render the "public" view or the "embed" view
|
||||
"""
|
||||
view = 'public_view'
|
||||
if is_embed:
|
||||
template = 'public_video_share_embed.html'
|
||||
else:
|
||||
template = 'public_video.html'
|
||||
|
||||
usage_key = UsageKey.from_string(usage_key_string)
|
||||
usage_key = usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))
|
||||
course_key = usage_key.course_key
|
||||
|
||||
if not PUBLIC_VIDEO_SHARE.is_enabled(course_key):
|
||||
raise Http404("Video not found.")
|
||||
|
||||
# usage key block type must be `video` else raise 404
|
||||
if usage_key.block_type != 'video':
|
||||
raise Http404("Video not found.")
|
||||
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course = get_course_by_id(course_key, 0)
|
||||
|
||||
block, _ = get_block_by_usage_id(
|
||||
request,
|
||||
str(course_key),
|
||||
str(usage_key),
|
||||
disable_staff_debug_info=True,
|
||||
course=course,
|
||||
will_recheck_access=False
|
||||
)
|
||||
|
||||
# Block must be marked as public to be viewed
|
||||
if not block.public_access:
|
||||
raise Http404("Video not found.")
|
||||
|
||||
fragment = block.render(view, context={
|
||||
'public_video_embed': is_embed,
|
||||
})
|
||||
|
||||
video_description = f"Watch a video from the course {course.display_name} "
|
||||
if course.display_organization is not None:
|
||||
video_description += f"by {course.display_organization} "
|
||||
video_description += "on edX.org"
|
||||
|
||||
video_poster = None
|
||||
if not is_embed:
|
||||
video_poster = block._poster() # pylint: disable=protected-access
|
||||
|
||||
context = {
|
||||
'fragment': fragment,
|
||||
'course': course,
|
||||
'video_title': block.display_name_with_default,
|
||||
'video_description': video_description,
|
||||
'video_thumbnail': video_poster if video_poster is not None else '',
|
||||
'video_embed_url': urljoin(
|
||||
settings.LMS_ROOT_URL,
|
||||
reverse('render_public_video_xblock_embed', kwargs={'usage_key_string': str(usage_key)})
|
||||
),
|
||||
'disable_accordion': False,
|
||||
'allow_iframing': True,
|
||||
'disable_header': False,
|
||||
'disable_footer': False,
|
||||
'disable_window_wrap': True,
|
||||
'edx_notes_enabled': False,
|
||||
'is_learning_mfe': True,
|
||||
'is_mobile_app': False,
|
||||
}
|
||||
return render_to_response(template, context)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@ensure_valid_usage_key
|
||||
@xframe_options_exempt
|
||||
@transaction.non_atomic_requests
|
||||
def render_public_video_xblock_embed(request, usage_key_string):
|
||||
"""
|
||||
Returns an HttpResponse with HTML content for the Video xBlock with the given usage_key.
|
||||
The returned HTML consists of nothing but the Video xBlock content for use in social media embedding.
|
||||
"""
|
||||
return _render_public_video_xblock(request, usage_key_string, is_embed=True)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@ensure_valid_usage_key
|
||||
@xframe_options_exempt
|
||||
@transaction.non_atomic_requests
|
||||
def render_public_video_xblock(request, usage_key_string):
|
||||
"""
|
||||
Returns an HttpResponse with HTML content for the Video xBlock with the given usage_key.
|
||||
The returned HTML is a chromeless rendering of the Video xBlock (excluding content of the containing courseware).
|
||||
"""
|
||||
return _render_public_video_xblock(request, usage_key_string, is_embed=False)
|
||||
|
||||
|
||||
def get_optimization_flags_for_content(block, fragment):
|
||||
"""
|
||||
Return a dict with a set of display options appropriate for the block.
|
||||
@@ -1816,6 +1720,166 @@ class XBlockContentInspector:
|
||||
return False
|
||||
|
||||
|
||||
class BasePublicVideoXBlockView(View):
|
||||
"""
|
||||
Base functionality for public video xblock view and embed view
|
||||
"""
|
||||
|
||||
@method_decorator(ensure_valid_usage_key)
|
||||
@method_decorator(xframe_options_exempt)
|
||||
@method_decorator(transaction.non_atomic_requests)
|
||||
def get(self, _, usage_key_string):
|
||||
""" Load course and video and render public view """
|
||||
course, video_block = self.get_course_and_video_block(usage_key_string)
|
||||
template, context = self.get_template_and_context(course, video_block)
|
||||
return render_to_response(template, context)
|
||||
|
||||
def get_course_and_video_block(self, usage_key_string):
|
||||
"""
|
||||
Load course and video from modulestore.
|
||||
Raises 404 if:
|
||||
- courseware.public_video_share waffle flag is not enabled for this course
|
||||
- block is not video
|
||||
- block is not marked as "public_access"
|
||||
"""
|
||||
usage_key = UsageKey.from_string(usage_key_string)
|
||||
usage_key = usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))
|
||||
course_key = usage_key.course_key
|
||||
|
||||
if not PUBLIC_VIDEO_SHARE.is_enabled(course_key):
|
||||
raise Http404("Video not found.")
|
||||
|
||||
# usage key block type must be `video` else raise 404
|
||||
if usage_key.block_type != 'video':
|
||||
raise Http404("Video not found.")
|
||||
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course = get_course_by_id(course_key, 0)
|
||||
|
||||
video_block, _ = get_block_by_usage_id(
|
||||
self.request,
|
||||
str(course_key),
|
||||
str(usage_key),
|
||||
disable_staff_debug_info=True,
|
||||
course=course,
|
||||
will_recheck_access=False
|
||||
)
|
||||
|
||||
# Block must be marked as public to be viewed
|
||||
if not video_block.public_access:
|
||||
raise Http404("Video not found.")
|
||||
|
||||
return course, video_block
|
||||
|
||||
|
||||
class PublicVideoXBlockView(BasePublicVideoXBlockView):
|
||||
""" View for displaying public videos """
|
||||
|
||||
def get_template_and_context(self, course, video_block):
|
||||
"""
|
||||
Render video xblock, gather social media metadata, and generate CTA links
|
||||
"""
|
||||
fragment = video_block.render('public_view', context={
|
||||
'public_video_embed': False,
|
||||
})
|
||||
course_about_page_url, enroll_url = self.get_public_video_cta_button_urls(course)
|
||||
social_sharing_metadata = self.get_social_sharing_metadata(course, video_block)
|
||||
context = {
|
||||
'fragment': fragment,
|
||||
'course': course,
|
||||
'social_sharing_metadata': social_sharing_metadata,
|
||||
'learn_more_url': course_about_page_url,
|
||||
'enroll_url': enroll_url,
|
||||
'disable_window_wrap': True,
|
||||
'disable_register_button': True,
|
||||
'edx_notes_enabled': False,
|
||||
'is_learning_mfe': True,
|
||||
'is_mobile_app': False,
|
||||
}
|
||||
return 'public_video.html', context
|
||||
|
||||
def get_social_sharing_metadata(self, course, video_block):
|
||||
"""
|
||||
Gather the information for the meta OpenGraph and Twitter-specific tags
|
||||
"""
|
||||
video_description = f"Watch a video from the course {course.display_name} "
|
||||
if course.display_organization is not None:
|
||||
video_description += f"by {course.display_organization} "
|
||||
video_description += "on edX.org"
|
||||
video_poster = video_block._poster() # pylint: disable=protected-access
|
||||
|
||||
return {
|
||||
'video_title': video_block.display_name_with_default,
|
||||
'video_description': video_description,
|
||||
'video_thumbnail': video_poster if video_poster is not None else '',
|
||||
'video_embed_url': urljoin(
|
||||
settings.LMS_ROOT_URL,
|
||||
reverse('render_public_video_xblock_embed', kwargs={'usage_key_string': str(video_block.location)})
|
||||
)
|
||||
}
|
||||
|
||||
def get_public_video_cta_button_urls(self, course):
|
||||
"""
|
||||
Get the links for the 'enroll' and 'learn more' buttons on the public video page
|
||||
"""
|
||||
course_key = str(course.id)
|
||||
utm_params = self.get_utm_params()
|
||||
course_about_page_url = self.build_url(
|
||||
reverse('about_course', kwargs={'course_id': course_key}), {}, utm_params
|
||||
)
|
||||
enroll_url = self.build_url(
|
||||
reverse('register_user'),
|
||||
{
|
||||
'course_id': course_key,
|
||||
'enrollment_action': 'enroll',
|
||||
'email_opt_in': False,
|
||||
},
|
||||
utm_params
|
||||
)
|
||||
return course_about_page_url, enroll_url
|
||||
|
||||
def get_utm_params(self):
|
||||
"""
|
||||
Helper function to pull all utm_ params from the request and return them as a dict
|
||||
"""
|
||||
utm_params = {}
|
||||
for param, value in self.request.GET.items():
|
||||
if param.startswith("utm_"):
|
||||
utm_params[param] = value
|
||||
return utm_params
|
||||
|
||||
def build_url(self, base_url, params, utm_params):
|
||||
"""
|
||||
Helper function to combine a base URL, params, and utm params into a full URL
|
||||
"""
|
||||
if not params and not utm_params:
|
||||
return base_url
|
||||
url_parts = urlparse(base_url)
|
||||
full_params = {**params, **utm_params}
|
||||
return urlunparse((
|
||||
url_parts.scheme,
|
||||
url_parts.netloc,
|
||||
url_parts.path,
|
||||
url_parts.params,
|
||||
urlencode(full_params),
|
||||
url_parts.fragment
|
||||
))
|
||||
|
||||
|
||||
class PublicVideoXBlockEmbedView(BasePublicVideoXBlockView):
|
||||
""" View for viewing public videos embedded within Twitter or other social media """
|
||||
def get_template_and_context(self, course, video_block):
|
||||
""" Render the embed view """
|
||||
fragment = video_block.render('public_view', context={
|
||||
'public_video_embed': True,
|
||||
})
|
||||
context = {
|
||||
'fragment': fragment,
|
||||
'course': course,
|
||||
}
|
||||
return 'public_video_share_embed.html', context
|
||||
|
||||
|
||||
# Translators: "percent_sign" is the symbol "%". "platform_name" is a
|
||||
# string identifying the name of this installation, such as "edX".
|
||||
FINANCIAL_ASSISTANCE_HEADER = _(
|
||||
|
||||
@@ -512,3 +512,38 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AU 972 Social Video Sharing Page
|
||||
.public-video-share-cta {
|
||||
position: relative;
|
||||
float: right;
|
||||
z-index: 1;
|
||||
|
||||
.btn-learn-more{
|
||||
@extend %btn-shims;
|
||||
color: #00262B;
|
||||
background: #FFFFFF;
|
||||
border-color: #F2F0EF;
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: darken(#FFFFFF, 7%);
|
||||
border-color: darken(#F2F0EF, 7%);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-enroll{
|
||||
@extend %btn-shims;
|
||||
color: #FFFFFF;
|
||||
background: #D23228;
|
||||
border-color: #D23228;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: darken(#FFFFFF, 7%);
|
||||
background: darken(#D23228, 7%);
|
||||
border-color: darken(#D23228, 7%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ ${HTML(fragment.foot_html())}
|
||||
|
||||
</%block>
|
||||
|
||||
<%block name="body_extra"/>
|
||||
|
||||
<div class="course-wrapper chromeless">
|
||||
<section class="course-content" id="course-content"\
|
||||
% if enable_completion_on_view_service:
|
||||
|
||||
@@ -48,7 +48,7 @@ from openedx.core.djangoapps.user_authn.toggles import should_redirect_to_authn_
|
||||
<div class="secondary">
|
||||
<div>
|
||||
% if allows_login:
|
||||
% if allow_public_account_creation:
|
||||
% if allow_public_account_creation and not disable_register_button:
|
||||
% if should_redirect_to_authn_mfe:
|
||||
<div class="mobile-nav-item hidden-mobile nav-item">
|
||||
<a class="register-btn btn" href="${settings.AUTHN_MICROFRONTEND_URL}/register${login_query()}">${_("Register for free")}</a>
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%!
|
||||
from django.utils.translation import gettext as _
|
||||
%>
|
||||
<%inherit file="courseware/courseware-chromeless.html"/>
|
||||
|
||||
<%block name="head_extra">
|
||||
<!-- OpenGraph tags -->
|
||||
<meta data-rh="true" property="og:type" content="website">
|
||||
<meta data-rh="true" property="og:site_name" content="edX">
|
||||
<meta data-rh="true" property="og:title" content=${video_title}>
|
||||
<meta data-rh="true" property="og:description" content="${video_description}">
|
||||
<meta data-rh="true" property="og:image" content="${video_thumbnail}">
|
||||
<meta data-rh="true" property="og:title" content="${social_sharing_metadata['video_title']}">
|
||||
<meta data-rh="true" property="og:description" content="${social_sharing_metadata['video_description']}">
|
||||
<meta data-rh="true" property="og:image" content="${social_sharing_metadata['video_thumbnail']}">
|
||||
|
||||
<!-- Twitter-specific video player tags -->
|
||||
<meta data-rh="true" name="twitter:card" content="player">
|
||||
<meta data-rh="true" name="twitter:site" content="@edxOnline">
|
||||
<meta data-rh="true" name="twitter:player" content=${video_embed_url}>
|
||||
<meta data-rh="true" name="twitter:player" content="${social_sharing_metadata['video_embed_url']}">
|
||||
<meta data-rh="true" name="twitter:player:width" content="1280">
|
||||
<meta data-rh="true" name="twitter:player:height" content="720">
|
||||
</%block>
|
||||
|
||||
<%block name="body_extra">
|
||||
<nav class="public-video-share-cta nav-links" aria-label="Learn More">
|
||||
<div>
|
||||
<a class="btn-learn-more btn" href="${learn_more_url}">
|
||||
${_("Learn more about this course")}
|
||||
</a>
|
||||
<a class="btn-enroll btn" href="${enroll_url}">
|
||||
${_("Enroll in this course")}
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</%block>
|
||||
|
||||
@@ -328,12 +328,12 @@ urlpatterns += [
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/{settings.USAGE_KEY_PATTERN}$',
|
||||
courseware_views.render_public_video_xblock,
|
||||
courseware_views.PublicVideoXBlockView.as_view(),
|
||||
name=RENDER_VIDEO_XBLOCK_NAME,
|
||||
),
|
||||
re_path(
|
||||
fr'^videos/{settings.USAGE_KEY_PATTERN}/embed$',
|
||||
courseware_views.render_public_video_xblock_embed,
|
||||
courseware_views.PublicVideoXBlockEmbedView.as_view(),
|
||||
name=RENDER_VIDEO_XBLOCK_EMBED_NAME,
|
||||
),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user