Commit 6f888d69 by Andy Armstrong

Add course certificates on the learner profile

LEARNER-1860
parent b31287e2
......@@ -107,8 +107,7 @@ cms/static/css/
cms/static/sass/*.css
cms/static/sass/*.css.map
cms/static/themed_sass/
themes/**/css/*.css
themes/**/css/discussion/*.css
themes/**/css
### Logging artifacts
log/
......
......@@ -245,6 +245,7 @@ def cert_info(user, course_overview, course_mode):
"""
if not course_overview.may_certify():
return {}
# Note: this should be rewritten to use the certificates API
return _cert_info(
user,
course_overview,
......
......@@ -7,3 +7,4 @@
@import 'base/reset';
@import 'base/variables';
@import 'base/mixins';
@import 'base/theme';
......@@ -6,3 +6,4 @@
@import 'base/reset';
@import 'base/variables';
@import 'base/mixins';
@import 'base/theme';
......@@ -4,6 +4,7 @@
// base - utilities
@import 'base/variables';
@import 'base/mixins';
@import 'base/theme';
footer#footer-edx-v3 {
@import 'base/extends';
......
......@@ -5,5 +5,6 @@
@import 'base/variables';
@import 'base/font_face';
@import 'base/mixins';
@import 'base/theme';
@import 'build-course'; // shared app style assets/rendering
......@@ -5,5 +5,6 @@
@import 'base/variables';
@import 'base/font_face';
@import 'base/mixins';
@import 'base/theme';
@import 'build-course'; // shared app style assets/rendering
......@@ -7,4 +7,4 @@
@import 'base/variables-rtl';
// Import shared build for the edx.org footer
@import 'build-footer-edx'
@import 'build-footer-edx';
......@@ -7,4 +7,4 @@
@import 'base/variables-ltr';
// Import shared build for the edx.org footer
@import 'build-footer-edx'
@import 'build-footer-edx';
......@@ -8,6 +8,7 @@
// base - utilities
@import 'base/variables';
@import 'base/mixins';
@import 'base/theme';
footer#footer-openedx {
@import 'base/reset';
......
......@@ -8,6 +8,7 @@
// base - utilities
@import 'base/variables';
@import 'base/mixins';
@import 'base/theme';
footer#footer-openedx {
@import 'base/reset';
......
// File to be overridden by themes
......@@ -248,11 +248,14 @@ $state-danger-border: darken($state-danger-bg, 5%) !default;
// ----------------------------
// logo colors
$micromasters-color: #005585;
$xseries-color: #424242;
$professional-certificate-color: #9a1f60;
$zebra-stripe-color: rgb(249, 250, 252);
$divider-color: rgb(226,231,236);
$audit-mode-color: $gray-dark !default;
$honor-mode-color: $uxpl-blue-base !default;
$verified-mode-color: $uxpl-green-base !default;
$micromasters-color: #005585 !default;
$xseries-color: #424242 !default;
$professional-certificate-color: #9a1f60 !default;
$zebra-stripe-color: rgb(249, 250, 252) !default;
$divider-color: rgb(226,231,236) !default;
// old color variables
// DEPRECATED: Do not continue to use these colors, instead use pattern libary and base colors above.
......
"""
Learner profile settings and helper methods.
"""
from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace
# Namespace for learner profile waffle flags.
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='learner_profile')
# Waffle flag to show achievements on the learner profile.
# TODO: LEARNER-2443: 08/2017: Remove flag after rollout.
SHOW_ACHIEVEMENTS_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_achievements')
<div class="message-banner" aria-live="polite"></div>
<div class="wrapper-profile">
<div class="ui-loading-indicator">
<p>
<div class="profile profile-other">
<div class="wrapper-profile-field-account-privacy"></div>
<div class="wrapper-profile-sections account-settings-container">
<div class="wrapper-profile-section-container-one">
<div class="wrapper-profile-section-one">
<div class="profile-image-field">
</div>
<div class="profile-section-one-fields">
</div>
</div>
<div class="ui-loading-error is-hidden">
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
<span class="copy">An error occurred. Try loading the page again.</span>
</div>
</div>
<div class="wrapper-profile-section-container-two">
<div class="wrapper-profile-bio">
</div>
</div>
</div>
</div>
<div class="ui-loading-indicator">
<p>
<span class="spin">
<span class="icon fa fa-refresh" aria-hidden="true"></span>
</span>
<span class="copy">
<span class="copy">
Loading
</span>
</p>
</div>
<div class="ui-loading-error is-hidden">
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
<span class="copy">
</p>
</div>
<div class="ui-loading-error is-hidden">
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
<span class="copy">
An error occurred. Please reload the page.
</span>
</div>
</div>
</div>
......@@ -36,14 +36,14 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
};
var expectProfilePrivacyFieldTobeRendered = function(learnerProfileView, othersProfile) {
var accountPrivacyElement = learnerProfileView.$('.wrapper-profile-field-account-privacy');
var privacyFieldElement = $(accountPrivacyElement).find('.u-field');
var $accountPrivacyElement = $('.wrapper-profile-field-account-privacy');
var $privacyFieldElement = $($accountPrivacyElement).find('.u-field');
if (othersProfile) {
expect(privacyFieldElement.length).toBe(0);
expect($privacyFieldElement.length).toBe(0);
} else {
expect(privacyFieldElement.length).toBe(1);
expectProfileElementContainsField(privacyFieldElement, learnerProfileView.options.accountPrivacyFieldView);
expect($privacyFieldElement.length).toBe(1);
expectProfileElementContainsField($privacyFieldElement, learnerProfileView.options.accountPrivacyFieldView);
}
};
......@@ -65,12 +65,12 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
};
var expectSectionTwoTobeRendered = function(learnerProfileView) {
var sectionTwoElement = learnerProfileView.$('.wrapper-profile-section-two');
var sectionTwoFieldElements = $(sectionTwoElement).find('.u-field');
var $sectionTwoElement = $('.wrapper-profile-section-two');
var $sectionTwoFieldElements = $($sectionTwoElement).find('.u-field');
expect(sectionTwoFieldElements.length).toBe(learnerProfileView.options.sectionTwoFieldViews.length);
expect($sectionTwoFieldElements.length).toBe(learnerProfileView.options.sectionTwoFieldViews.length);
_.each(sectionTwoFieldElements, function(sectionFieldElement, fieldIndex) {
_.each($sectionTwoFieldElements, function(sectionFieldElement, fieldIndex) {
expectProfileElementContainsField(
sectionFieldElement,
learnerProfileView.options.sectionTwoFieldViews[fieldIndex]
......@@ -85,7 +85,7 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
};
var expectLimitedProfileSectionsAndFieldsToBeRendered = function(learnerProfileView, othersProfile) {
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
var sectionOneFieldElements = $('.wrapper-profile-section-one').find('.u-field');
expectProfilePrivacyFieldTobeRendered(learnerProfileView, othersProfile);
......@@ -108,9 +108,9 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
};
var expectProfileSectionsNotToBeRendered = function(learnerProfileView) {
expect(learnerProfileView.$('.wrapper-profile-field-account-privacy').length).toBe(0);
expect(learnerProfileView.$('.wrapper-profile-section-one').length).toBe(0);
expect(learnerProfileView.$('.wrapper-profile-section-two').length).toBe(0);
expect($('.wrapper-profile-field-account-privacy').length).toBe(0);
expect($('.wrapper-profile-section-one').length).toBe(0);
expect($('.wrapper-profile-section-two').length).toBe(0);
};
var expectTabbedViewToBeUndefined = function(requests, tabbedViewView) {
......@@ -124,42 +124,42 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
};
var expectBadgesDisplayed = function(learnerProfileView, length, lastPage) {
var badgeListingView = learnerProfileView.$el.find('#tabpanel-accomplishments'),
var $badgeListingView = $('#tabpanel-accomplishments'),
updatedLength = length,
placeholder;
expect(learnerProfileView.$el.find('#tabpanel-about_me').hasClass('is-hidden')).toBe(true);
expect(badgeListingView.hasClass('is-hidden')).toBe(false);
expect($('#tabpanel-about_me').hasClass('is-hidden')).toBe(true);
expect($badgeListingView.hasClass('is-hidden')).toBe(false);
if (lastPage) {
updatedLength += 1;
placeholder = badgeListingView.find('.find-course');
placeholder = $badgeListingView.find('.find-course');
expect(placeholder.length).toBe(1);
expect(placeholder.attr('href')).toBe('/courses/');
}
expect(badgeListingView.find('.badge-display').length).toBe(updatedLength);
expect($badgeListingView.find('.badge-display').length).toBe(updatedLength);
};
var expectBadgesHidden = function(learnerProfileView) {
var accomplishmentsTab = learnerProfileView.$el.find('#tabpanel-accomplishments');
if (accomplishmentsTab.length) {
var $accomplishmentsTab = $('#tabpanel-accomplishments');
if ($accomplishmentsTab.length) {
// Nonexistence counts as hidden.
expect(learnerProfileView.$el.find('#tabpanel-accomplishments').hasClass('is-hidden')).toBe(true);
expect($('#tabpanel-accomplishments').hasClass('is-hidden')).toBe(true);
}
expect(learnerProfileView.$el.find('#tabpanel-about_me').hasClass('is-hidden')).toBe(false);
expect($('#tabpanel-about_me').hasClass('is-hidden')).toBe(false);
};
var expectPage = function(learnerProfileView, pageData) {
var badgeListContainer = learnerProfileView.$el.find('#tabpanel-accomplishments');
var index = badgeListContainer.find('span.search-count').text().trim();
var $badgeListContainer = $('#tabpanel-accomplishments');
var index = $badgeListContainer.find('span.search-count').text().trim();
expect(index).toBe('Showing ' + (pageData.start + 1) + '-' + (pageData.start + pageData.results.length) +
' out of ' + pageData.count + ' total');
expect(badgeListContainer.find('.current-page').text()).toBe('' + pageData.current_page);
expect($badgeListContainer.find('.current-page').text()).toBe('' + pageData.current_page);
_.each(pageData.results, function(badge) {
expect($('.badge-display:contains(' + badge.badge_class.display_name + ')').length).toBe(1);
});
};
var expectBadgeLoadingErrorIsRendered = function(learnerProfileView) {
var errorMessage = learnerProfileView.$el.find('.badge-set-display').text();
var errorMessage = $('.badge-set-display').text();
expect(errorMessage).toBe(
'Your request could not be completed. Reload the page and try again. If the issue persists, click the ' +
'Help tab to report the problem.'
......
......@@ -5,11 +5,9 @@
[
'gettext', 'jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/html-utils',
'common/js/components/views/tabbed_view',
'learner_profile/js/views/section_two_tab',
'text!learner_profile/templates/learner_profile.underscore',
'edx-ui-toolkit/js/utils/string-utils'
'learner_profile/js/views/section_two_tab'
],
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab, learnerProfileTemplate, StringUtils) {
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab) {
var LearnerProfileView = Backbone.View.extend({
initialize: function(options) {
......@@ -25,8 +23,6 @@
this.firstRender = true;
},
template: _.template(learnerProfileTemplate),
showFullProfile: function() {
var isAboveMinimumAge = this.options.accountSettingsModel.isAboveMinimumAge();
if (this.options.ownProfile) {
......@@ -54,22 +50,13 @@
ownProfile: this.options.ownProfile
});
HtmlUtils.setHtml(this.$el, HtmlUtils.template(learnerProfileTemplate)({
username: self.options.accountSettingsModel.get('username'),
name: self.options.accountSettingsModel.get('name'),
ownProfile: self.options.ownProfile,
showFullProfile: self.showFullProfile(),
profile_header: gettext('My Profile'),
profile_subheader:
StringUtils.interpolate(
gettext('Build out your profile to personalize your identity on {platform_name}.'), {
platform_name: self.options.platformName
}
)
}));
this.renderFields();
// Reveal the profile and hide the loading indicator
$('.ui-loading-indicator').addClass('is-hidden');
$('.wrapper-profile-section-container-one').removeClass('is-hidden');
$('.wrapper-profile-section-container-two').removeClass('is-hidden');
if (this.showFullProfile() && (this.options.accountSettingsModel.get('accomplishments_shared'))) {
tabs = [
{view: this.sectionTwoView, title: gettext('About Me'), url: 'about_me'},
......@@ -108,7 +95,8 @@
Backbone.history.start();
}
} else {
this.$el.find('.wrapper-profile-section-container-two').append(this.sectionTwoView.render().el);
// xss-lint: disable=javascript-jquery-html
this.$el.find('.wrapper-profile-bio').html(this.sectionTwoView.render().el);
}
return this;
},
......
<div class="profile <%- ownProfile ? 'profile-self' : 'profile-other' %>">
<div class="wrapper-profile-field-account-privacy"></div>
<div class="wrapper-profile-sections account-settings-container">
<% if (ownProfile) { %>
<div class="profile-header">
<div class="header"> <%- profile_header %></div>
<div class="subheader"> <%- profile_subheader %></div>
</div>
<% } %>
<div class="wrapper-profile-section-container-one">
<div class="wrapper-profile-section-one">
<div class="profile-image-field">
</div>
<div class="profile-section-one-fields">
</div>
</div>
<div class="ui-loading-error is-hidden">
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
<span class="copy"><%- gettext("An error occurred. Try loading the page again.") %></span>
</div>
</div>
<div class="wrapper-profile-section-container-two">
</div>
</div>
</div>
## mako
<%page expression_filter="h"/>
<%namespace name='static' file='/static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import HTML, Text
%>
<div class="learner-achievements">
% if course_certificates or own_profile:
<h3 class="u-field-title">Course Certificates</h3>
% if course_certificates:
% for certificate in course_certificates:
<%
certificate_url = certificate['download_url']
course = certificate['course']
completion_date_message_html = Text(_('Completed {completion_date_html}')).format(
completion_date=HTML(
'<span'
' class="localized-datetime start-date"'
' data-datetime="{completion_date_html}"'
' data-format="shortDate"'
' data-timezone="{user_timezone}"'
' data-language="{user_language}"'
'></span>'
).format(
completion_date=certificate['created'],
user_timezone=user_timezone,
user_language=user_language,
),
)
%>
% if certificate_url:
<a href="${certificate_url}" target="_blank">
<div class="card certificate-card mode-${certificate['type']}">
<div class="card-logo">
<h4 class="sr-only">
${_('{course_mode} certificate').format(
course_mode=certificate['type'],
)}
</h4>
</div>
<div class="card-content">
<div class="card-supertitle">${course.display_org_with_default}</div>
<div class="card-title">${course.display_name_with_default}</div>
<p class="card-text">${completion_date_message_html}</p>
</div>
</div>
</a>
% else:
<div class="card certificate-card mode-${certificate['type']}">
<div class="card-logo">
<h4 class="sr-only">
${_('{course_mode} certificate').format(
course_mode=certificate['type'],
)}
</h4>
</div>
<div class="card-content">
<div class="card-supertitle">${course.display_org_with_default}</div>
<div class="card-title">${course.display_name_with_default}</div>
<p class="card-text">${completion_date_message_html}</p>
</div>
</div>
% endif
% endfor
% elif own_profile:
<div class="learner-message">
<h4 class="message-header">${_("You haven't earned any certificates yet.")}</h4>
<p class="message-actions">
<a class="btn btn-brand" href="${marketing_link('COURSES')}">
<span class="icon fa fa-search" aria-hidden="true"></span>
${_('Explore New Courses')}
</a>
</p>
</div>
% endif
% endif
</div>
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform('.localized-datetime');
</%static:require_module_async>
......@@ -4,28 +4,65 @@
<%inherit file="/main.html" />
<%def name="online_help_token()"><% return "profile" %></%def>
<%namespace name='static' file='/static_content.html'/>
<%!
import json
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from openedx.core.djangolib.markup import HTML
%>
<%block name="pagetitle">${_("Learner Profile")}</%block>
<%block name="bodyclass">view-profile</%block>
<%block name="headextra">
<%static:css group='style-course'/>
</%block>
<div class="message-banner" aria-live="polite"></div>
<main id="main" aria-label="Content" tabindex="-1">
<div class="wrapper-profile">
<div class="ui-loading-indicator">
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
<div class="profile ${'profile-self' if own_profile else 'profile-other'}">
<div class="wrapper-profile-field-account-privacy"></div>
<div class="wrapper-profile-sections account-settings-container">
% if own_profile:
<div class="profile-header">
<div class="header">${_("My Profile")}</div>
<div class="subheader">
${_('Build out your profile to personalize your identity on {platform_name}.').format(
platform_name=platform_name,
)}
</div>
</div>
% endif
<div class="ui-loading-indicator">
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
</div>
<div class="wrapper-profile-section-container-one is-hidden">
<div class="wrapper-profile-section-one">
<div class="profile-image-field">
</div>
<div class="profile-section-one-fields">
</div>
</div>
<div class="ui-loading-error is-hidden">
<span class="fa fa-exclamation-triangle message-error" aria-hidden="true"></span>
<span class="copy">${_("An error occurred. Try loading the page again.")}</span>
</div>
</div>
<div class="wrapper-profile-section-container-two is-hidden">
% if achievements_fragment:
${HTML(achievements_fragment.body_html())}
% endif
<div class="wrapper-profile-bio">
</div>
</div>
</div>
</div>
</div>
</main>
<%block name="headextra">
<%static:css group='style-course'/>
</%block>
<%block name="js_extra">
<%static:require_module module_name="learner_profile/js/learner_profile_factory" class_name="LearnerProfileFactory">
......
# -*- coding: utf-8 -*-
""" Tests for student profile views. """
import datetime
import ddt
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.client import RequestFactory
from student.tests.factories import UserFactory
from util.testing import UrlResetMixin
from ..views import learner_profile_context
from course_modes.models import CourseMode
from certificates.tests.factories import GeneratedCertificateFactory # pylint: disable=import-error
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from openedx.features.learner_profile.views.learner_profile import learner_profile_context
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
class LearnerProfileViewTest(UrlResetMixin, TestCase):
@ddt.ddt
class LearnerProfileViewTest(UrlResetMixin, ModuleStoreTestCase):
""" Tests for the student profile view. """
USERNAME = "username"
OTHER_USERNAME = "other_user"
PASSWORD = "password"
DOWNLOAD_URL = "http://www.example.com/certificate.pdf"
CONTEXT_DATA = [
'default_public_account_fields',
'accounts_api_url',
......@@ -32,7 +43,13 @@ class LearnerProfileViewTest(UrlResetMixin, TestCase):
def setUp(self):
super(LearnerProfileViewTest, self).setUp()
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
self.other_user = UserFactory.create(username=self.OTHER_USERNAME, password=self.PASSWORD)
self.client.login(username=self.USERNAME, password=self.PASSWORD)
self.course = CourseFactory.create(
start=datetime.datetime(2013, 9, 16, 7, 17, 28),
end=datetime.datetime.now(),
certificate_available_date=datetime.datetime.now(),
)
def test_context(self):
"""
......@@ -100,3 +117,48 @@ class LearnerProfileViewTest(UrlResetMixin, TestCase):
profile_path = reverse('learner_profile', kwargs={'username': "no_such_user"})
response = self.client.get(path=profile_path)
self.assertEqual(404, response.status_code)
def _create_certificate(self, enrollment_mode):
"""Simulate that the user has a generated certificate. """
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, mode=enrollment_mode)
return GeneratedCertificateFactory(
user=self.user,
course_id=self.course.id,
mode=enrollment_mode,
download_url=self.DOWNLOAD_URL,
status="downloadable"
)
@ddt.data(CourseMode.HONOR, CourseMode.PROFESSIONAL, CourseMode.VERIFIED)
def test_certificate_visibility(self, cert_mode):
"""
Verify that certificates are displayed with the correct card mode.
"""
# Add new certificate
cert = self._create_certificate(cert_mode)
cert.save()
request = RequestFactory().get('/url')
request.user = self.user
context = learner_profile_context(request, self.user.username, self.user.is_staff)
self.assertTrue('card certificate-card mode-' + cert_mode in str(context['achievements_fragment'].content))
@ddt.data(True, False)
def test_no_certificate_visibility(self, own_profile):
"""
Verify that the 'You haven't earned any certificates yet.' well appears on the user's
own profile when they do not have certificates and does not appear when viewing
another user that does not have any certificates.
"""
request = RequestFactory().get('/url')
request.user = self.user
profile_username = self.user.username if own_profile else self.other_user.username
context = learner_profile_context(request, profile_username, self.user.is_staff)
if own_profile:
content = str(context['achievements_fragment'].content)
self.assertIn('icon fa fa-search', content)
self.assertIn("You haven't earned any certificates yet", content)
else:
self.assertIsNone(context['achievements_fragment'])
......@@ -5,12 +5,19 @@ Defines URLs for the learner profile.
from django.conf import settings
from django.conf.urls import url
from views.learner_achievements import LearnerAchievementsFragmentView
urlpatterns = [
url(
r'^{username_pattern}$'.format(
username_pattern=settings.USERNAME_PATTERN,
),
'openedx.features.learner_profile.views.learner_profile',
'openedx.features.learner_profile.views.learner_profile.learner_profile',
name='learner_profile',
),
url(
r'^achievements$',
LearnerAchievementsFragmentView.as_view(),
name='openedx.learner_profile.learner_achievements_fragment_view',
),
]
"""
Views to render a learner's achievements.
"""
from courseware.courses import get_course_overview_with_access
from django.template.loader import render_to_string
from lms.djangoapps.certificates import api as certificate_api
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from web_fragments.fragment import Fragment
class LearnerAchievementsFragmentView(EdxFragmentView):
"""
A fragment to render a learner's achievements.
"""
def render_to_fragment(self, request, username=None, own_profile=False, **kwargs):
"""
Renders the current learner's achievements.
"""
course_certificates = self._get_ordered_certificates_for_user(request, username)
context = {
'course_certificates': course_certificates,
'own_profile': own_profile,
'disable_courseware_js': True,
}
if course_certificates or own_profile:
html = render_to_string('learner_profile/learner-achievements-fragment.html', context)
return Fragment(html)
else:
return None
def _get_ordered_certificates_for_user(self, request, username):
"""
Returns a user's certificates sorted by course name.
"""
course_certificates = certificate_api.get_certificates_for_user(username)
for course_certificate in course_certificates:
course_key = course_certificate['course_key']
course_overview = get_course_overview_with_access(request.user, 'load', course_key)
course_certificate['course'] = course_overview
course_certificates.sort(key=lambda certificate: certificate['course'].display_name_with_default)
return course_certificates
......@@ -17,6 +17,10 @@ from openedx.core.djangoapps.user_api.errors import UserNotAuthorized, UserNotFo
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
from student.models import User
from .. import SHOW_ACHIEVEMENTS_FLAG
from learner_achievements import LearnerAchievementsFragmentView
@login_required
@require_http_methods(['GET'])
......@@ -70,7 +74,19 @@ def learner_profile_context(request, profile_username, user_is_staff):
preferences_data = get_user_preferences(profile_user, profile_username)
if SHOW_ACHIEVEMENTS_FLAG.is_enabled():
achievements_fragment = LearnerAchievementsFragmentView().render_to_fragment(
request,
username=profile_user.username,
own_profile=own_profile,
)
else:
achievements_fragment = None
context = {
'own_profile': own_profile,
'achievements_fragment': achievements_fragment,
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
'data': {
'profile_user_id': profile_user.id,
'default_public_account_fields': settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'],
......
// Certificate overrides for edge.edx.org
.certificate-card {
// Note: edx.org no longer supports audit certificates, but there are
// legacy certificates that might be rendered. In this situation, they
// are styled as honor certificates.
&.mode-honor, &.mode-audit {
border-color: $honor-mode-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/honor.png');
}
}
&.mode-verified {
border-color: $verified-mode-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/verified.png');
}
}
&.mode-professional {
border-color: $professional-certificate-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/professional.png');
}
}
}
// Theme overrides for edge.edx.org
@import 'base/certificates';
// Certificate overrides for edx.org
.certificate-card {
// Note: edx.org no longer supports audit certificates, but there are
// legacy certificates that might be rendered. In this situation, they
// are styled as honor certificates.
&.mode-honor, &.mode-audit {
border-color: $honor-mode-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/honor.png');
}
}
&.mode-verified {
border-color: $verified-mode-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/verified.png');
}
}
&.mode-professional {
border-color: $professional-certificate-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/professional.png');
}
}
}
// Theme overrides for edx.org
@import 'base/certificates';
// Certificate overrides for the red theme
.certificate-card {
&.mode-audit {
border-color: $audit-mode-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
}
}
&.mode-honor {
border-color: $honor-mode-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
}
}
&.mode-verified {
border-color: $verified-mode-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
}
}
&.mode-professional {
border-color: $professional-certificate-color;
.card-logo {
background-image: url('#{$static-path}/images/certificates/red-certificate.png');
}
}
}
// Theme overrides for the red theme
@import 'base/certificates';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment