Course teams occasionally upload very large files as their course image. Before this commit, those images would be used directly in the student's dashboard, sometimes leading to MBs worth of image data on that page. With this commit, we now auto-generate small and large thumbnails of configurable size. The Student Dashboard and Course About pages will make use of this new functionality (CourseOverview.image_urls), but the behavior of CourseOverview.course_image_url will not change. Note that the thumbnails are still created in the contentstore, and sit alongside their originals. What's included: 1. Multiple sizes, currently starting with "raw", "small", and "large". This falls back to the current behavior automatically in the case where thumbnails don't exist or this feature has been disabled in configuration. 2. Django admin based configuration for image sizes and whether to enable the functionality at all. Note that to regenerate images, you'd need to wipe the CourseOverviewImageSet model rows -- it doesn't do that automatically. This is partly because it's a very rare operation, and partly because I'm not entirely sure what the longer term invalidation strategy should be in a world where we might potentially have multiple themes. The flexible configuration was intended to allow better customization and theming. 3. The Course About pages also use the new thumbnail functionality, as an example of "large". This is in addition to the "small" used on the student dashboard. Things I'm punting on for now (followup PRs welcome!): 1. Bringing the thumbnails to course discovery. A quick attempt to do so showed that it wasn't getting properly invalidated and updated when publishes happen (so the old image still showed up). It probably has something to do with when we do the re-indexing because it stores this data in elasticsearch, but I'm not going to chase it down right now. 2. Center-cropping. While this is a nice-to-have feature, the behavior in this PR is no worse than what already exists in master in terms of image distortion (letting the browser handle it). 3. Automated invalidation of the images when a new config is created.
372 lines
17 KiB
HTML
372 lines
17 KiB
HTML
<%namespace name='static' file='../static_content.html'/>
|
|
<%!
|
|
from microsite_configuration import microsite
|
|
from django.utils.translation import ugettext as _
|
|
from django.core.urlresolvers import reverse
|
|
from courseware.courses import get_course_about_section
|
|
from django.conf import settings
|
|
from edxmako.shortcuts import marketing_link
|
|
from openedx.core.lib.courses import course_image_url
|
|
%>
|
|
|
|
<%inherit file="../main.html" />
|
|
<%block name="headextra">
|
|
## OG (Open Graph) title and description added below to give social media info to display
|
|
## (https://developers.facebook.com/docs/opengraph/howtos/maximizing-distribution-media-content#tags)
|
|
<meta property="og:title" content="${course.display_name_with_default}" />
|
|
<meta property="og:description" content="${get_course_about_section(request, course, 'short_description')}" />
|
|
</%block>
|
|
|
|
<%block name="js_extra">
|
|
|
|
<script type="text/javascript">
|
|
(function() {
|
|
$(".register").click(function(event) {
|
|
$("#class_enroll_form").submit();
|
|
event.preventDefault();
|
|
});
|
|
|
|
% if can_add_course_to_cart:
|
|
add_course_complete_handler = function(jqXHR, textStatus) {
|
|
if (jqXHR.status == 200) {
|
|
location.href = "${cart_link}";
|
|
}
|
|
if (jqXHR.status == 400) {
|
|
$("#register_error")
|
|
.html(jqXHR.responseText ? jqXHR.responseText : "${_("An error occurred. Please try again later.")}")
|
|
.css("display", "block");
|
|
}
|
|
else if (jqXHR.status == 403) {
|
|
location.href = "${reg_then_add_to_cart_link}";
|
|
}
|
|
};
|
|
$("#add_to_cart_post").click(function(event){
|
|
$.ajax({
|
|
url: "${reverse('add_course_to_cart', args=[course.id.to_deprecated_string()])}",
|
|
type: "POST",
|
|
/* Rant: HAD TO USE COMPLETE B/C PROMISE.DONE FOR SOME REASON DOES NOT WORK ON THIS PAGE. */
|
|
complete: add_course_complete_handler
|
|
})
|
|
event.preventDefault();
|
|
});
|
|
% endif
|
|
|
|
## making the conditional around this entire JS block for sanity
|
|
%if settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
|
|
<%
|
|
perms_error = _('The currently logged-in user account does not have permission to enroll in this course. '
|
|
'You may need to {start_logout_tag}log out{end_tag} then try the enroll button again. '
|
|
'Please visit the {start_help_tag}help page{end_tag} for a possible solution.').format(
|
|
start_help_tag="<a href='{url}'>".format(url=marketing_link('FAQ')), end_tag='</a>',
|
|
start_logout_tag="<a href='{url}'>".format(url=reverse('logout'))
|
|
)
|
|
%>
|
|
$('#class_enroll_form').on('ajax:complete', function(event, xhr) {
|
|
if(xhr.status == 200) {
|
|
location.href = "${reverse('dashboard')}";
|
|
} else if (xhr.status == 403) {
|
|
location.href = "${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}?course_id=${course.id | u}&enrollment_action=enroll";
|
|
} else if (xhr.status == 400) { //This means the user did not have permission
|
|
$('#register_error').html("${perms_error}").css("display", "block");
|
|
} else {
|
|
$('#register_error').html(
|
|
(xhr.responseText ? xhr.responseText : "${_("An error occurred. Please try again later.")}")
|
|
).css("display", "block");
|
|
}
|
|
});
|
|
|
|
%else:
|
|
|
|
$('#class_enroll_form').on('ajax:complete', function(event, xhr) {
|
|
if(xhr.status == 200) {
|
|
if (xhr.responseText == "") {
|
|
location.href = "${reverse('dashboard')}";
|
|
}
|
|
else {
|
|
location.href = xhr.responseText;
|
|
}
|
|
} else if (xhr.status == 403) {
|
|
location.href = "${reverse('register_user')}?course_id=${course.id | u}&enrollment_action=enroll";
|
|
} else {
|
|
$('#register_error').html(
|
|
(xhr.responseText ? xhr.responseText : "${_("An error occurred. Please try again later.")}")
|
|
).css("display", "block");
|
|
}
|
|
});
|
|
|
|
%endif
|
|
|
|
})(this)
|
|
</script>
|
|
|
|
<script src="${static.url('js/course_info.js')}"></script>
|
|
</%block>
|
|
|
|
<%block name="pagetitle">${course.display_name_with_default}</%block>
|
|
|
|
<section class="course-info">
|
|
<header class="course-profile">
|
|
<div class="intro-inner-wrapper">
|
|
<div class="table">
|
|
<section class="intro">
|
|
<hgroup>
|
|
<h1>
|
|
${course.display_name_with_default}
|
|
% if not self.theme_enabled():
|
|
<a href="#">${course.display_org_with_default | h}</a>
|
|
% endif
|
|
</h1>
|
|
</hgroup>
|
|
|
|
<div class="main-cta">
|
|
%if user.is_authenticated() and registered:
|
|
%if show_courseware_link:
|
|
<a href="${course_target}">
|
|
%endif
|
|
|
|
<span class="register disabled">${_("You are enrolled in this course")}</span>
|
|
|
|
%if show_courseware_link:
|
|
<strong>${_("View Courseware")}</strong>
|
|
</a>
|
|
%endif
|
|
|
|
%elif in_cart:
|
|
<span class="add-to-cart">
|
|
${_('This course is in your <a href="{cart_link}">cart</a>.').format(cart_link=cart_link)}
|
|
</span>
|
|
% elif is_course_full:
|
|
<span class="register disabled">
|
|
${_("Course is full")}
|
|
</span>
|
|
% elif invitation_only and not can_enroll:
|
|
<span class="register disabled">${_("Enrollment in this course is by invitation only")}</span>
|
|
## Shib courses need the enrollment button to be displayed even when can_enroll is False,
|
|
## because AnonymousUsers cause can_enroll for shib courses to be False, but we need them to be able to click
|
|
## so that they can register and become a real user that can enroll.
|
|
% elif not is_shib_course and not can_enroll:
|
|
<span class="register disabled">${_("Enrollment is Closed")}</span>
|
|
%elif can_add_course_to_cart:
|
|
<%
|
|
if user.is_authenticated():
|
|
reg_href = "#"
|
|
reg_element_id = "add_to_cart_post"
|
|
else:
|
|
reg_href = reg_then_add_to_cart_link
|
|
reg_element_id = "reg_then_add_to_cart"
|
|
%>
|
|
<a href="${reg_href}" class="add-to-cart" id="${reg_element_id}">
|
|
${_("Add {course_name} to Cart <span>({price} USD)</span>")\
|
|
.format(course_name=course.display_number_with_default, price=course_price)}
|
|
|
|
</a>
|
|
<div id="register_error"></div>
|
|
%else:
|
|
<a href="#" class="register">
|
|
${_("Enroll in {course_name}").format(course_name=course.display_number_with_default) | h}
|
|
</a>
|
|
<div id="register_error"></div>
|
|
%endif
|
|
</div>
|
|
|
|
</section>
|
|
% if get_course_about_section(request, course, "video"):
|
|
<a href="#video-modal" class="media" rel="leanModal">
|
|
<div class="hero">
|
|
<img src="${course_image_urls['large']}" alt="" />
|
|
<div class="play-intro"></div>
|
|
</div>
|
|
</a>
|
|
%else:
|
|
<div class="media">
|
|
<div class="hero">
|
|
<img src="${course_image_urls['large']}" alt="" />
|
|
</div>
|
|
</div>
|
|
% endif
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<section class="container">
|
|
<section class="details">
|
|
% if staff_access and studio_url is not None:
|
|
<div class="wrap-instructor-info studio-view">
|
|
<a class="instructor-info-action" href="${studio_url}">${_("View About Page in studio")}</a>
|
|
</div>
|
|
% endif
|
|
|
|
<nav aria-label="${_('Course About')}">
|
|
<a href="#" class="active">${_("Overview")}</a>
|
|
## <a href="#">${_("FAQ")}</a>
|
|
## <a href="#">${_("Requirements")}</a>
|
|
## <a href="#">${_("Text-book")}</a>
|
|
## <a href="#">${_("Syllabus")}</a>
|
|
## <a href="#">${_("Reviews")}</a>
|
|
</nav>
|
|
|
|
<div class="inner-wrapper">
|
|
${get_course_about_section(request, course, "overview")}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="course-sidebar">
|
|
<section class="course-summary">
|
|
<header>
|
|
% if microsite.get_value('course_about_show_social_links', True):
|
|
<div class="social-sharing">
|
|
<div class="sharing-message">${_("Share with friends and family!")}</div>
|
|
## TODO: this should probably be an overrideable block,
|
|
## or something allowing themes to do whatever they
|
|
## want here (and on this whole page, really).
|
|
% if self.stanford_theme_enabled():
|
|
<a href="http://twitter.com/intent/tweet?text=I+just+enrolled+in+${course.number}+${course.display_name_with_default}!+(http://class.stanford.edu)" class="share">
|
|
<i class="icon fa fa-twitter"></i><span class="sr">${_("Tweet that you've enrolled in this course")}</span>
|
|
</a>
|
|
<a href="mailto:?subject=Take%20a%20course%20at%20Stanford%20online!&body=I%20just%20enrolled%20in%20${course.number}%20${course.display_name_with_default}+(http://class.stanford.edu)" class="share">
|
|
<i class="icon fa fa-envelope"></i><span class="sr">${_("Email someone to say you've enrolled in this course")}</span>
|
|
</a>
|
|
% else:
|
|
<%
|
|
site_domain = microsite.get_value('site_domain', settings.SITE_NAME)
|
|
platform_name = microsite.get_value('platform_name', settings.PLATFORM_NAME)
|
|
|
|
## Translators: This text will be automatically posted to the student's
|
|
## Twitter account. {url} should appear at the end of the text.
|
|
tweet_text = _("I just enrolled in {number} {title} through {account}: {url}").format(
|
|
number=course.number,
|
|
title=course.display_name_with_default,
|
|
account=microsite.get_value('course_about_twitter_account', settings.PLATFORM_TWITTER_ACCOUNT),
|
|
url=u"http://{domain}{path}".format(
|
|
domain=site_domain,
|
|
path=reverse('about_course', args=[course.id.to_deprecated_string()])
|
|
)
|
|
).replace(u" ", u"+")
|
|
tweet_action = u"http://twitter.com/intent/tweet?text={tweet_text}".format(tweet_text=tweet_text)
|
|
|
|
facebook_link = microsite.get_value('course_about_facebook_link', settings.PLATFORM_FACEBOOK_ACCOUNT)
|
|
|
|
email_subject = u"mailto:?subject={subject}&body={body}".format(
|
|
subject=_("Take a course with {platform} online").format(platform=platform_name),
|
|
body=_("I just enrolled in {number} {title} through {platform} {url}").format(
|
|
number=course.number,
|
|
title=course.display_name_with_default,
|
|
platform=platform_name,
|
|
url=u"http://{domain}{path}".format(
|
|
domain=site_domain,
|
|
path=reverse('about_course', args=[course.id.to_deprecated_string()]),
|
|
)
|
|
)
|
|
).replace(u" ", u"%20")
|
|
%>
|
|
<a href="${tweet_action}" class="share">
|
|
<i class="icon fa fa-twitter"></i><span class="sr">${_("Tweet that you've enrolled in this course")}</span>
|
|
</a>
|
|
<a href="${facebook_link}" class="share">
|
|
<i class="icon fa fa-thumbs-up"></i><span class="sr">${_("Post a Facebook message to say you've enrolled in this course")}</span>
|
|
</a>
|
|
<a href="${email_subject}" class="share">
|
|
<i class="icon fa fa-envelope"></i><span class="sr">${_("Email someone to say you've enrolled in this course")}</span>
|
|
</a>
|
|
% endif
|
|
</div>
|
|
% endif
|
|
</header>
|
|
|
|
<ol class="important-dates">
|
|
<li class="important-dates-item"><i class="icon fa fa-info-circle"></i><p class="important-dates-item-title">${_("Course Number")}</p><span class="important-dates-item-text course-number">${course.display_number_with_default | h}</span></li>
|
|
% if not course.start_date_is_still_default:
|
|
<li class="important-dates-item"><i class="icon fa fa-calendar"></i><p class="important-dates-item-title">${_("Classes Start")}</p><span class="important-dates-item-text start-date">${course.start_datetime_text()}</span></li>
|
|
% endif
|
|
## We plan to ditch end_date (which is not stored in course metadata),
|
|
## but for backwards compatibility, show about/end_date blob if it exists.
|
|
% if get_course_about_section(request, course, "end_date") or course.end:
|
|
<li class="important-dates-item">
|
|
<i class="icon fa fa-calendar"></i>
|
|
<p class="important-dates-item-title">${_("Classes End")}</p>
|
|
<span class="important-dates-item-text final-date">
|
|
% if get_course_about_section(request, course, "end_date"):
|
|
${get_course_about_section(request, course, "end_date")}
|
|
% else:
|
|
${course.end_datetime_text()}
|
|
% endif
|
|
</span>
|
|
</li>
|
|
% endif
|
|
|
|
% if get_course_about_section(request, course, "effort"):
|
|
<li class="important-dates-item"><i class="icon fa fa-pencil"></i><p class="important-dates-item-title">${_("Estimated Effort")}</p><span class="important-dates-item-text effort">${get_course_about_section(request, course, "effort")}</span></li>
|
|
% endif
|
|
|
|
##<li class="important-dates-item"><i class="icon fa fa-clock-o"></i><p class="important-dates-item-title">${_('Course Length')}</p><span class="important-dates-item-text course-length">${_('{number} weeks').format(number=15)}</span></li>
|
|
|
|
%if course_price and (can_add_course_to_cart or is_cosmetic_price_enabled):
|
|
<li class="important-dates-item">
|
|
<i class="icon fa fa-money"></i>
|
|
<p class="important-dates-item-title">${_("Price")}</p>
|
|
<span class="important-dates-item-text">${course_price}</span>
|
|
</li>
|
|
%endif
|
|
|
|
% if pre_requisite_courses:
|
|
<% prc_target = reverse('about_course', args=[unicode(pre_requisite_courses[0]['key'])]) %>
|
|
<li class="prerequisite-course important-dates-item">
|
|
<i class="icon fa fa-list-ul"></i>
|
|
<p class="important-dates-item-title">${_("Prerequisites")}</p>
|
|
## Multiple pre-requisite courses are not supported on frontend that's why we are pulling first element
|
|
<span class="important-dates-item-text pre-requisite"><a href="${prc_target}">${pre_requisite_courses[0]['display']}</a></span>
|
|
<p class="tip">
|
|
${_("You must successfully complete {link_start}{prc_display}{link_end} before you begin this course.").format(
|
|
link_start='<a href="{}">'.format(prc_target),
|
|
link_end='</a>',
|
|
prc_display=pre_requisite_courses[0]['display'],
|
|
)}
|
|
</p>
|
|
</li>
|
|
% endif
|
|
% if get_course_about_section(request, course, "prerequisites"):
|
|
<li class="important-dates-item"><i class="icon fa fa-book"></i><p class="important-dates-item-title">${_("Requirements")}</p><span class="important-dates-item-text prerequisites">${get_course_about_section(request, course, "prerequisites")}</span></li>
|
|
% endif
|
|
</ol>
|
|
</section>
|
|
|
|
|
|
## For now, ocw links are the only thing that goes in additional resources
|
|
% if get_course_about_section(request, course, "ocw_links"):
|
|
<section class="additional-resources">
|
|
<header>
|
|
<h1>${_("Additional Resources")}</h1>
|
|
</header>
|
|
|
|
<section>
|
|
## "MITOpenCourseware" should *not* be translated
|
|
<h2 class="opencourseware">MITOpenCourseware</h2>
|
|
${get_course_about_section(request, course, "ocw_links")}
|
|
</section>
|
|
</section>
|
|
%endif
|
|
</section>
|
|
|
|
</section>
|
|
</section>
|
|
|
|
## Need to put this hidden form on the page so that the registration button works.
|
|
## Since it's no harm to display a hidden form, we display it with the most permissive conditional
|
|
## which is when the student is not registered.
|
|
%if active_reg_button or is_shib_course:
|
|
<div style="display: none;">
|
|
<form id="class_enroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
|
|
<fieldset class="enroll_fieldset">
|
|
<legend class="sr">${_("Enroll")}</legend>
|
|
<input name="course_id" type="hidden" value="${course.id | h}">
|
|
<input name="enrollment_action" type="hidden" value="enroll">
|
|
</fieldset>
|
|
<div class="submit">
|
|
<input name="submit" type="submit" value="${_('enroll')}">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
%endif
|
|
|
|
<%include file="../video_modal.html" />
|