feat!: Legacy account, profile, order history removal (#36219)

* feat!: Legacy account, profile, order history removal

This removes the legacy account and profile applications, and the order
history page. This is primarily a reapplication of #31893, which was
rolled back due to prior blockers.

FIXES: APER-3884
FIXES: openedx/public-engineering#71


Co-authored-by: Muhammad Abdullah Waheed <42172960+abdullahwaheed@users.noreply.github.com>
Co-authored-by: Bilal Qamar <59555732+BilalQamar95@users.noreply.github.com>
This commit is contained in:
Deborah Kaplan
2025-02-10 14:39:13 -05:00
committed by GitHub
parent 36c16d6952
commit 29de9b2dc4
73 changed files with 752 additions and 7391 deletions

View File

@@ -1,8 +0,0 @@
Learner Profile
---------------
This directory contains a Django application that provides a view to render
a profile for any Open edX learner. See `Exploring Your Dashboard and Profile`_
for more details.
.. _Exploring Your Dashboard and Profile: https://edx.readthedocs.io/projects/open-edx-learner-guide/en/latest/SFD_dashboard_profile_SectionHead.html?highlight=profile

View File

@@ -1,40 +0,0 @@
<div class="message-banner" aria-live="polite"></div>
<div class="wrapper-profile">
<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">
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">
An error occurred. Please reload the page.
</span>
</div>
</div>

View File

@@ -1,219 +0,0 @@
(function(define) {
'use strict';
define([
'gettext',
'jquery',
'underscore',
'backbone',
'logger',
'edx-ui-toolkit/js/utils/string-utils',
'edx-ui-toolkit/js/pagination/paging-collection',
'js/student_account/models/user_account_model',
'js/student_account/models/user_preferences_model',
'js/views/fields',
'learner_profile/js/views/learner_profile_fields',
'learner_profile/js/views/learner_profile_view',
'js/student_account/views/account_settings_fields',
'js/views/message_banner',
'string_utils'
], function(gettext, $, _, Backbone, Logger, StringUtils, PagingCollection, AccountSettingsModel,
AccountPreferencesModel, FieldsView, LearnerProfileFieldsView, LearnerProfileView,
AccountSettingsFieldViews, MessageBannerView) {
return function(options) {
var $learnerProfileElement = $('.wrapper-profile');
var accountSettingsModel = new AccountSettingsModel(
_.extend(
options.account_settings_data,
{
default_public_account_fields: options.default_public_account_fields,
parental_consent_age_limit: options.parental_consent_age_limit,
enable_coppa_compliance: options.enable_coppa_compliance
}
),
{parse: true}
);
var AccountPreferencesModelWithDefaults = AccountPreferencesModel.extend({
defaults: {
account_privacy: options.default_visibility
}
});
var accountPreferencesModel = new AccountPreferencesModelWithDefaults(options.preferences_data);
var editable = options.own_profile ? 'toggle' : 'never';
var messageView = new MessageBannerView({
el: $('.message-banner')
});
var accountPrivacyFieldView,
profileImageFieldView,
usernameFieldView,
nameFieldView,
sectionOneFieldViews,
sectionTwoFieldViews,
learnerProfileView,
getProfileVisibility,
showLearnerProfileView;
accountSettingsModel.url = options.accounts_api_url;
accountPreferencesModel.url = options.preferences_api_url;
accountPrivacyFieldView = new LearnerProfileFieldsView.AccountPrivacyFieldView({
model: accountPreferencesModel,
required: true,
editable: 'always',
showMessages: false,
title: gettext('Profile Visibility:'),
valueAttribute: 'account_privacy',
options: [
['private', gettext('Limited Profile')],
['all_users', gettext('Full Profile')]
],
helpMessage: '',
accountSettingsPageUrl: options.account_settings_page_url,
persistChanges: true
});
profileImageFieldView = new LearnerProfileFieldsView.ProfileImageFieldView({
model: accountSettingsModel,
valueAttribute: 'profile_image',
editable: editable === 'toggle',
messageView: messageView,
imageMaxBytes: options.profile_image_max_bytes,
imageMinBytes: options.profile_image_min_bytes,
imageUploadUrl: options.profile_image_upload_url,
imageRemoveUrl: options.profile_image_remove_url
});
usernameFieldView = new FieldsView.ReadonlyFieldView({
model: accountSettingsModel,
screenReaderTitle: gettext('Username'),
valueAttribute: 'username',
helpMessage: ''
});
nameFieldView = new FieldsView.ReadonlyFieldView({
model: accountSettingsModel,
screenReaderTitle: gettext('Full Name'),
valueAttribute: 'name',
helpMessage: ''
});
sectionOneFieldViews = [
new LearnerProfileFieldsView.SocialLinkIconsView({
model: accountSettingsModel,
socialPlatforms: options.social_platforms,
ownProfile: options.own_profile
}),
new FieldsView.DateFieldView({
title: gettext('Joined'),
titleVisible: true,
model: accountSettingsModel,
screenReaderTitle: gettext('Joined Date'),
valueAttribute: 'date_joined',
helpMessage: '',
userLanguage: accountSettingsModel.get('language'),
userTimezone: accountPreferencesModel.get('time_zone'),
dateFormat: 'MMMM YYYY' // not localized, but hopefully ok.
}),
new FieldsView.DropdownFieldView({
title: gettext('Location'),
titleVisible: true,
model: accountSettingsModel,
screenReaderTitle: gettext('Country'),
required: true,
editable: editable,
showMessages: false,
placeholderValue: gettext('Add Country'),
valueAttribute: 'country',
options: options.country_options,
helpMessage: '',
persistChanges: true
}),
new AccountSettingsFieldViews.LanguageProficienciesFieldView({
title: gettext('Language'),
titleVisible: true,
model: accountSettingsModel,
screenReaderTitle: gettext('Preferred Language'),
required: false,
editable: editable,
showMessages: false,
placeholderValue: gettext('Add language'),
valueAttribute: 'language_proficiencies',
options: options.language_options,
helpMessage: '',
persistChanges: true
})
];
sectionTwoFieldViews = [
new FieldsView.TextareaFieldView({
model: accountSettingsModel,
editable: editable,
showMessages: false,
title: gettext('About me'),
// eslint-disable-next-line max-len
placeholderValue: gettext("Tell other learners a little about yourself: where you live, what your interests are, why you're taking courses, or what you hope to learn."),
valueAttribute: 'bio',
helpMessage: '',
persistChanges: true,
messagePosition: 'header',
maxCharacters: 300
})
];
learnerProfileView = new LearnerProfileView({
el: $learnerProfileElement,
ownProfile: options.own_profile,
has_preferences_access: options.has_preferences_access,
accountSettingsModel: accountSettingsModel,
preferencesModel: accountPreferencesModel,
accountPrivacyFieldView: accountPrivacyFieldView,
profileImageFieldView: profileImageFieldView,
usernameFieldView: usernameFieldView,
nameFieldView: nameFieldView,
sectionOneFieldViews: sectionOneFieldViews,
sectionTwoFieldViews: sectionTwoFieldViews,
platformName: options.platform_name
});
getProfileVisibility = function() {
if (options.has_preferences_access) {
return accountPreferencesModel.get('account_privacy');
} else {
return accountSettingsModel.get('profile_is_public') ? 'all_users' : 'private';
}
};
showLearnerProfileView = function() {
// Record that the profile page was viewed
Logger.log('edx.user.settings.viewed', {
page: 'profile',
visibility: getProfileVisibility(),
user_id: options.profile_user_id
});
// Render the view for the first time
learnerProfileView.render();
};
if (options.has_preferences_access) {
if (accountSettingsModel.get('requires_parental_consent')) {
accountPreferencesModel.set('account_privacy', 'private');
}
}
showLearnerProfileView();
return {
accountSettingsModel: accountSettingsModel,
accountPreferencesModel: accountPreferencesModel,
learnerProfileView: learnerProfileView,
};
};
});
}).call(this, define || RequireJS.define);

View File

@@ -1,79 +0,0 @@
define(
[
'backbone', 'jquery', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/template_helpers',
'js/spec/student_account/helpers',
'learner_profile/js/spec_helpers/helpers',
'js/views/fields',
'js/student_account/models/user_account_model',
'js/student_account/models/user_preferences_model',
'learner_profile/js/views/learner_profile_view',
'learner_profile/js/views/learner_profile_fields',
'learner_profile/js/learner_profile_factory',
'js/views/message_banner'
],
function(Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews,
UserAccountModel, UserPreferencesModel, LearnerProfileView, LearnerProfileFields, LearnerProfilePage) {
'use strict';
describe('edx.user.LearnerProfileFactory', function() {
var createProfilePage;
beforeEach(function() {
loadFixtures('learner_profile/fixtures/learner_profile.html');
});
afterEach(function() {
Backbone.history.stop();
});
createProfilePage = function(ownProfile, options) {
return new LearnerProfilePage({
accounts_api_url: Helpers.USER_ACCOUNTS_API_URL,
preferences_api_url: Helpers.USER_PREFERENCES_API_URL,
own_profile: ownProfile,
account_settings_page_url: Helpers.USER_ACCOUNTS_API_URL,
country_options: Helpers.FIELD_OPTIONS,
language_options: Helpers.FIELD_OPTIONS,
has_preferences_access: true,
profile_image_max_bytes: Helpers.IMAGE_MAX_BYTES,
profile_image_min_bytes: Helpers.IMAGE_MIN_BYTES,
profile_image_upload_url: Helpers.IMAGE_UPLOAD_API_URL,
profile_image_remove_url: Helpers.IMAGE_REMOVE_API_URL,
default_visibility: 'all_users',
platform_name: 'edX',
find_courses_url: '/courses/',
account_settings_data: Helpers.createAccountSettingsData(options),
preferences_data: Helpers.createUserPreferencesData()
});
};
it('renders the full profile for a user', function() {
var context,
learnerProfileView;
AjaxHelpers.requests(this);
context = createProfilePage(true);
learnerProfileView = context.learnerProfileView;
// sets the profile for full view.
context.accountPreferencesModel.set({account_privacy: 'all_users'});
LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView, false);
});
it("renders the limited profile for undefined 'year_of_birth'", function() {
var context = createProfilePage(true, {year_of_birth: '', requires_parental_consent: true}),
learnerProfileView = context.learnerProfileView;
LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView);
});
it('renders the limited profile for under 13 users', function() {
var context = createProfilePage(
true,
{year_of_birth: new Date().getFullYear() - 10, requires_parental_consent: true}
);
var learnerProfileView = context.learnerProfileView;
LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView);
});
});
});

View File

@@ -1,381 +0,0 @@
define(
[
'backbone',
'jquery',
'underscore',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/template_helpers',
'js/spec/student_account/helpers',
'js/student_account/models/user_account_model',
'learner_profile/js/views/learner_profile_fields',
'js/views/message_banner'
],
function(Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, UserAccountModel, LearnerProfileFields,
MessageBannerView) {
'use strict';
describe('edx.user.LearnerProfileFields', function() {
var MOCK_YEAR_OF_BIRTH = 1989;
var MOCK_IMAGE_MAX_BYTES = 64;
var MOCK_IMAGE_MIN_BYTES = 16;
var createImageView = function(options) {
var yearOfBirth = _.isUndefined(options.yearOfBirth) ? MOCK_YEAR_OF_BIRTH : options.yearOfBirth;
var imageMaxBytes = _.isUndefined(options.imageMaxBytes) ? MOCK_IMAGE_MAX_BYTES : options.imageMaxBytes;
var imageMinBytes = _.isUndefined(options.imageMinBytes) ? MOCK_IMAGE_MIN_BYTES : options.imageMinBytes;
var messageView;
var imageData = {
image_url_large: '/media/profile-images/default.jpg',
has_image: !!options.hasImage
};
var accountSettingsModel = new UserAccountModel();
accountSettingsModel.set({profile_image: imageData});
accountSettingsModel.set({year_of_birth: yearOfBirth});
accountSettingsModel.set({requires_parental_consent: !!_.isEmpty(yearOfBirth)});
accountSettingsModel.url = Helpers.USER_ACCOUNTS_API_URL;
messageView = new MessageBannerView({
el: $('.message-banner')
});
return new LearnerProfileFields.ProfileImageFieldView({
model: accountSettingsModel,
valueAttribute: 'profile_image',
editable: options.ownProfile,
messageView: messageView,
imageMaxBytes: imageMaxBytes,
imageMinBytes: imageMinBytes,
imageUploadUrl: Helpers.IMAGE_UPLOAD_API_URL,
imageRemoveUrl: Helpers.IMAGE_REMOVE_API_URL
});
};
var createSocialLinksView = function(ownProfile, socialPlatformLinks) {
var accountSettingsModel = new UserAccountModel();
accountSettingsModel.set({social_platforms: socialPlatformLinks});
return new LearnerProfileFields.SocialLinkIconsView({
model: accountSettingsModel,
socialPlatforms: ['twitter', 'facebook', 'linkedin'],
ownProfile: ownProfile
});
};
var createFakeImageFile = function(size) {
var fileFakeData = 'i63ljc6giwoskyb9x5sw0169bdcmcxr3cdz8boqv0lik971972cmd6yknvcxr5sw0nvc169bdcmcxsdf';
return new Blob(
[fileFakeData.substr(0, size)],
{type: 'image/jpg'}
);
};
var initializeUploader = function(view) {
view.$('.upload-button-input').fileupload({
url: Helpers.IMAGE_UPLOAD_API_URL,
type: 'POST',
add: view.fileSelected,
done: view.imageChangeSucceeded,
fail: view.imageChangeFailed
});
};
beforeEach(function() {
loadFixtures('learner_profile/fixtures/learner_profile.html');
TemplateHelpers.installTemplate('templates/fields/field_image');
TemplateHelpers.installTemplate('templates/fields/message_banner');
TemplateHelpers.installTemplate('learner_profile/templates/social_icons');
});
afterEach(function() {
// image_field.js's window.onBeforeUnload breaks Karma in Chrome, clean it up after each test
$(window).off('beforeunload');
});
describe('ProfileImageFieldView', function() {
var verifyImageUploadButtonMessage = function(view, inProgress) {
var iconName = inProgress ? 'fa-spinner' : 'fa-camera';
var message = inProgress ? view.titleUploading : view.uploadButtonTitle();
expect(view.$('.upload-button-icon span').attr('class')).toContain(iconName);
expect(view.$('.upload-button-title').text().trim()).toBe(message);
};
var verifyImageRemoveButtonMessage = function(view, inProgress) {
var iconName = inProgress ? 'fa-spinner' : 'fa-remove';
var message = inProgress ? view.titleRemoving : view.removeButtonTitle();
expect(view.$('.remove-button-icon span').attr('class')).toContain(iconName);
expect(view.$('.remove-button-title').text().trim()).toBe(message);
};
it('can upload profile image', function() {
var requests = AjaxHelpers.requests(this);
var imageName = 'profile_image.jpg';
var imageView = createImageView({ownProfile: true, hasImage: false});
var data;
imageView.render();
initializeUploader(imageView);
// Remove button should not be present for default image
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
// For default image, image title should be `Upload an image`
verifyImageUploadButtonMessage(imageView, false);
// Add image to upload queue. Validate the image size and send POST request to upload image
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]});
// Verify image upload progress message
verifyImageUploadButtonMessage(imageView, true);
// Verify if POST request received for image upload
AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_UPLOAD_API_URL, new FormData());
// Send 204 NO CONTENT to confirm the image upload success
AjaxHelpers.respondWithNoContent(requests);
// Upon successful image upload, account settings model will be fetched to
// get the url for newly uploaded image, So we need to send the response for that GET
data = {
profile_image: {
image_url_large: '/media/profile-images/' + imageName,
has_image: true
}
};
AjaxHelpers.respondWithJson(requests, data);
// Verify uploaded image name
expect(imageView.$('.image-frame').attr('src')).toContain(imageName);
// Remove button should be present after successful image upload
expect(imageView.$('.u-field-remove-button').css('display') !== 'none').toBeTruthy();
// After image upload, image title should be `Change image`
verifyImageUploadButtonMessage(imageView, false);
});
it('can remove profile image', function() {
var requests = AjaxHelpers.requests(this);
var imageView = createImageView({ownProfile: true, hasImage: false});
var data;
imageView.render();
// Verify image remove title
verifyImageRemoveButtonMessage(imageView, false);
imageView.$('.u-field-remove-button').click();
// Verify image remove progress message
verifyImageRemoveButtonMessage(imageView, true);
// Verify if POST request received for image remove
AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_REMOVE_API_URL, null);
// Send 204 NO CONTENT to confirm the image removal success
AjaxHelpers.respondWithNoContent(requests);
// Upon successful image removal, account settings model will be fetched to get default image url
// So we need to send the response for that GET
data = {
profile_image: {
image_url_large: '/media/profile-images/default.jpg',
has_image: false
}
};
AjaxHelpers.respondWithJson(requests, data);
// Remove button should not be present for default image
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
});
it("can't remove default profile image", function() {
var imageView = createImageView({ownProfile: true, hasImage: false});
imageView.render();
spyOn(imageView, 'clickedRemoveButton');
// Remove button should not be present for default image
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
imageView.$('.u-field-remove-button').click();
// Remove button click handler should not be called
expect(imageView.clickedRemoveButton).not.toHaveBeenCalled();
});
it("can't upload image having size greater than max size", function() {
var imageView = createImageView({ownProfile: true, hasImage: false});
imageView.render();
initializeUploader(imageView);
// Add image to upload queue, this will validate the image size
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(70)]});
// Verify error message
expect($('.message-banner').text().trim())
.toBe('The file must be smaller than 64 bytes in size.');
});
it("can't upload image having size less than min size", function() {
var imageView = createImageView({ownProfile: true, hasImage: false});
imageView.render();
initializeUploader(imageView);
// Add image to upload queue, this will validate the image size
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(10)]});
// Verify error message
expect($('.message-banner').text().trim()).toBe('The file must be at least 16 bytes in size.');
});
it("can't upload and remove image if parental consent required", function() {
var imageView = createImageView({ownProfile: true, hasImage: false, yearOfBirth: ''});
imageView.render();
spyOn(imageView, 'clickedUploadButton');
spyOn(imageView, 'clickedRemoveButton');
expect(imageView.$('.u-field-upload-button').css('display') === 'none').toBeTruthy();
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
imageView.$('.u-field-upload-button').click();
imageView.$('.u-field-remove-button').click();
expect(imageView.clickedUploadButton).not.toHaveBeenCalled();
expect(imageView.clickedRemoveButton).not.toHaveBeenCalled();
});
it("can't upload and remove image on others profile", function() {
var imageView = createImageView({ownProfile: false});
imageView.render();
spyOn(imageView, 'clickedUploadButton');
spyOn(imageView, 'clickedRemoveButton');
expect(imageView.$('.u-field-upload-button').css('display') === 'none').toBeTruthy();
expect(imageView.$('.u-field-remove-button').css('display') === 'none').toBeTruthy();
imageView.$('.u-field-upload-button').click();
imageView.$('.u-field-remove-button').click();
expect(imageView.clickedUploadButton).not.toHaveBeenCalled();
expect(imageView.clickedRemoveButton).not.toHaveBeenCalled();
});
it('shows message if we try to navigate away during image upload/remove', function() {
var imageView = createImageView({ownProfile: true, hasImage: false});
spyOn(imageView, 'onBeforeUnload');
imageView.render();
initializeUploader(imageView);
// Add image to upload queue, this will validate image size and send POST request to upload image
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]});
// Verify image upload progress message
verifyImageUploadButtonMessage(imageView, true);
window.onbeforeunload = null;
$(window).trigger('beforeunload');
expect(imageView.onBeforeUnload).toHaveBeenCalled();
});
it('shows error message for HTTP 500', function() {
var requests = AjaxHelpers.requests(this);
var imageView = createImageView({ownProfile: true, hasImage: false});
imageView.render();
initializeUploader(imageView);
// Add image to upload queue. Validate the image size and send POST request to upload image
imageView.$('.upload-button-input').fileupload('add', {files: [createFakeImageFile(60)]});
// Verify image upload progress message
verifyImageUploadButtonMessage(imageView, true);
// Verify if POST request received for image upload
AjaxHelpers.expectRequest(requests, 'POST', Helpers.IMAGE_UPLOAD_API_URL, new FormData());
// Send HTTP 500
AjaxHelpers.respondWithError(requests);
expect($('.message-banner').text().trim()).toBe(imageView.errorMessage);
});
});
describe('SocialLinkIconsView', function() {
var socialPlatformLinks,
socialLinkData,
socialLinksView,
socialPlatform,
$icon;
it('icons are visible and links to social profile if added in account settings', function() {
socialPlatformLinks = {
twitter: {
platform: 'twitter',
social_link: 'https://www.twitter.com/edX'
},
facebook: {
platform: 'facebook',
social_link: 'https://www.facebook.com/edX'
},
linkedin: {
platform: 'linkedin',
social_link: ''
}
};
socialLinksView = createSocialLinksView(true, socialPlatformLinks);
// Icons should be present and contain links if defined
for (var i = 0; i < Object.keys(socialPlatformLinks); i++) { // eslint-disable-line vars-on-top
socialPlatform = Object.keys(socialPlatformLinks)[i];
socialLinkData = socialPlatformLinks[socialPlatform];
if (socialLinkData.social_link) {
// Icons with a social_link value should be displayed with a surrounding link
$icon = socialLinksView.$('span.fa-' + socialPlatform + '-square');
expect($icon).toExist();
expect($icon.parent().is('a'));
} else {
// Icons without a social_link value should be displayed without a surrounding link
$icon = socialLinksView.$('span.fa-' + socialPlatform + '-square');
expect($icon).toExist();
expect(!$icon.parent().is('a'));
}
}
});
it('icons are not visible on a profile with no links', function() {
socialPlatformLinks = {
twitter: {
platform: 'twitter',
social_link: ''
},
facebook: {
platform: 'facebook',
social_link: ''
},
linkedin: {
platform: 'linkedin',
social_link: ''
}
};
socialLinksView = createSocialLinksView(false, socialPlatformLinks);
// Icons should not be present if not defined on another user's profile
for (var i = 0; i < Object.keys(socialPlatformLinks); i++) { // eslint-disable-line vars-on-top
socialPlatform = Object.keys(socialPlatformLinks)[i];
socialLinkData = socialPlatformLinks[socialPlatform];
$icon = socialLinksView.$('span.fa-' + socialPlatform + '-square');
expect($icon).toBe(null);
}
});
});
});
});

View File

@@ -1,218 +0,0 @@
/* eslint-disable vars-on-top */
define(
[
'gettext',
'backbone',
'jquery',
'underscore',
'edx-ui-toolkit/js/pagination/paging-collection',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/template_helpers',
'js/spec/student_account/helpers',
'learner_profile/js/spec_helpers/helpers',
'js/views/fields',
'js/student_account/models/user_account_model',
'js/student_account/models/user_preferences_model',
'learner_profile/js/views/learner_profile_fields',
'learner_profile/js/views/learner_profile_view',
'js/student_account/views/account_settings_fields',
'js/views/message_banner'
],
function(gettext, Backbone, $, _, PagingCollection, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers,
FieldViews, UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView,
AccountSettingsFieldViews, MessageBannerView) {
'use strict';
describe('edx.user.LearnerProfileView', function() {
var createLearnerProfileView = function(ownProfile, accountPrivacy, profileIsPublic) {
var accountSettingsModel = new UserAccountModel();
accountSettingsModel.set(Helpers.createAccountSettingsData());
accountSettingsModel.set({profile_is_public: profileIsPublic});
accountSettingsModel.set({profile_image: Helpers.PROFILE_IMAGE});
var accountPreferencesModel = new AccountPreferencesModel();
accountPreferencesModel.set({account_privacy: accountPrivacy});
accountPreferencesModel.url = Helpers.USER_PREFERENCES_API_URL;
var editable = ownProfile ? 'toggle' : 'never';
var accountPrivacyFieldView = new LearnerProfileFields.AccountPrivacyFieldView({
model: accountPreferencesModel,
required: true,
editable: 'always',
showMessages: false,
title: 'edX learners can see my:',
valueAttribute: 'account_privacy',
options: [
['all_users', 'Full Profile'],
['private', 'Limited Profile']
],
helpMessage: '',
accountSettingsPageUrl: '/account/settings/'
});
var messageView = new MessageBannerView({
el: $('.message-banner')
});
var profileImageFieldView = new LearnerProfileFields.ProfileImageFieldView({
model: accountSettingsModel,
valueAttribute: 'profile_image',
editable: editable,
messageView: messageView,
imageMaxBytes: Helpers.IMAGE_MAX_BYTES,
imageMinBytes: Helpers.IMAGE_MIN_BYTES,
imageUploadUrl: Helpers.IMAGE_UPLOAD_API_URL,
imageRemoveUrl: Helpers.IMAGE_REMOVE_API_URL
});
var usernameFieldView = new FieldViews.ReadonlyFieldView({
model: accountSettingsModel,
valueAttribute: 'username',
helpMessage: ''
});
var nameFieldView = new FieldViews.ReadonlyFieldView({
model: accountSettingsModel,
valueAttribute: 'name',
helpMessage: ''
});
var sectionOneFieldViews = [
new LearnerProfileFields.SocialLinkIconsView({
model: accountSettingsModel,
socialPlatforms: Helpers.SOCIAL_PLATFORMS,
ownProfile: true
}),
new FieldViews.DropdownFieldView({
title: gettext('Location'),
model: accountSettingsModel,
required: false,
editable: editable,
showMessages: false,
placeholderValue: '',
valueAttribute: 'country',
options: Helpers.FIELD_OPTIONS,
helpMessage: ''
}),
new AccountSettingsFieldViews.LanguageProficienciesFieldView({
title: gettext('Language'),
model: accountSettingsModel,
required: false,
editable: editable,
showMessages: false,
placeholderValue: 'Add language',
valueAttribute: 'language_proficiencies',
options: Helpers.FIELD_OPTIONS,
helpMessage: ''
}),
new FieldViews.DateFieldView({
model: accountSettingsModel,
valueAttribute: 'date_joined',
helpMessage: ''
})
];
var sectionTwoFieldViews = [
new FieldViews.TextareaFieldView({
model: accountSettingsModel,
editable: editable,
showMessages: false,
title: 'About me',
placeholderValue: 'Tell other edX learners a little about yourself: where you live, '
+ "what your interests are, why you're taking courses on edX, or what you hope to learn.",
valueAttribute: 'bio',
helpMessage: '',
messagePosition: 'header'
})
];
return new LearnerProfileView(
{
el: $('.wrapper-profile'),
ownProfile: ownProfile,
hasPreferencesAccess: true,
accountSettingsModel: accountSettingsModel,
preferencesModel: accountPreferencesModel,
accountPrivacyFieldView: accountPrivacyFieldView,
usernameFieldView: usernameFieldView,
nameFieldView: nameFieldView,
profileImageFieldView: profileImageFieldView,
sectionOneFieldViews: sectionOneFieldViews,
sectionTwoFieldViews: sectionTwoFieldViews,
});
};
beforeEach(function() {
loadFixtures('learner_profile/fixtures/learner_profile.html');
});
afterEach(function() {
Backbone.history.stop();
});
it('shows loading error correctly', function() {
var learnerProfileView = createLearnerProfileView(false, 'all_users');
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
learnerProfileView.render();
learnerProfileView.showLoadingError();
Helpers.expectLoadingErrorIsVisible(learnerProfileView, true);
});
it('renders all fields as expected for self with full access', function() {
var learnerProfileView = createLearnerProfileView(true, 'all_users', true);
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
learnerProfileView.render();
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView);
});
it('renders all fields as expected for self with limited access', function() {
var learnerProfileView = createLearnerProfileView(true, 'private', false);
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
learnerProfileView.render();
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView);
});
it('renders the fields as expected for others with full access', function() {
var learnerProfileView = createLearnerProfileView(false, 'all_users', true);
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
learnerProfileView.render();
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
LearnerProfileHelpers.expectProfileSectionsAndFieldsToBeRendered(learnerProfileView, true);
});
it('renders the fields as expected for others with limited access', function() {
var learnerProfileView = createLearnerProfileView(false, 'private', false);
Helpers.expectLoadingIndicatorIsVisible(learnerProfileView, true);
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
learnerProfileView.render();
Helpers.expectLoadingErrorIsVisible(learnerProfileView, false);
LearnerProfileHelpers.expectLimitedProfileSectionsAndFieldsToBeRendered(learnerProfileView, true);
});
});
});

View File

@@ -1,113 +0,0 @@
/* eslint-disable vars-on-top */
define(
[
'backbone', 'jquery', 'underscore',
'js/spec/student_account/helpers',
'learner_profile/js/views/section_two_tab',
'js/views/fields',
'js/student_account/models/user_account_model'
],
function(Backbone, $, _, Helpers, SectionTwoTabView, FieldViews, UserAccountModel) {
'use strict';
describe('edx.user.SectionTwoTab', function() {
var createSectionTwoView = function(ownProfile, profileIsPublic) {
var accountSettingsModel = new UserAccountModel();
accountSettingsModel.set(Helpers.createAccountSettingsData());
accountSettingsModel.set({profile_is_public: profileIsPublic});
accountSettingsModel.set({profile_image: Helpers.PROFILE_IMAGE});
var editable = ownProfile ? 'toggle' : 'never';
var sectionTwoFieldViews = [
new FieldViews.TextareaFieldView({
model: accountSettingsModel,
editable: editable,
showMessages: false,
title: 'About me',
placeholderValue: 'Tell other edX learners a little about yourself: where you live, '
+ "what your interests are, why you're taking courses on edX, or what you hope to learn.",
valueAttribute: 'bio',
helpMessage: '',
messagePosition: 'header'
})
];
return new SectionTwoTabView({
viewList: sectionTwoFieldViews,
showFullProfile: function() {
return profileIsPublic;
},
ownProfile: ownProfile
});
};
it('full profile displayed for public profile', function() {
var view = createSectionTwoView(false, true);
view.render();
var bio = view.$el.find('.u-field-bio');
expect(bio.length).toBe(1);
});
it('profile field parts are actually rendered for public profile', function() {
var view = createSectionTwoView(false, true);
_.each(view.options.viewList, function(fieldView) {
spyOn(fieldView, 'render').and.callThrough();
});
view.render();
_.each(view.options.viewList, function(fieldView) {
expect(fieldView.render).toHaveBeenCalled();
});
});
var testPrivateProfile = function(ownProfile, messageString) {
var view = createSectionTwoView(ownProfile, false);
view.render();
var bio = view.$el.find('.u-field-bio');
expect(bio.length).toBe(0);
var msg = view.$el.find('span.profile-private-message');
expect(msg.length).toBe(1);
expect(_.count(msg.html(), messageString)).toBeTruthy();
};
it('no profile when profile is private for other people', function() {
testPrivateProfile(false, 'This learner is currently sharing a limited profile');
});
it('no profile when profile is private for the user herself', function() {
testPrivateProfile(true, 'You are currently sharing a limited profile');
});
var testProfilePrivatePartsDoNotRender = function(ownProfile) {
var view = createSectionTwoView(ownProfile, false);
_.each(view.options.viewList, function(fieldView) {
spyOn(fieldView, 'render');
});
view.render();
_.each(view.options.viewList, function(fieldView) {
expect(fieldView.render).not.toHaveBeenCalled();
});
};
it('profile field parts are not rendered for private profile for owner', function() {
testProfilePrivatePartsDoNotRender(true);
});
it('profile field parts are not rendered for private profile for other people', function() {
testProfilePrivatePartsDoNotRender(false);
});
it('does not allow fields to be edited when visiting a profile for other people', function() {
var view = createSectionTwoView(false, true);
var bio = view.options.viewList[0];
expect(bio.editable).toBe('never');
});
it("allows fields to be edited when visiting one's own profile", function() {
var view = createSectionTwoView(true, true);
var bio = view.options.viewList[0];
expect(bio.editable).toBe('toggle');
});
});
}
);

View File

@@ -1,133 +0,0 @@
define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'], function(_, URI, AjaxHelpers) {
'use strict';
var expectProfileElementContainsField = function(element, view) {
var titleElement, fieldTitle;
var $element = $(element);
// Avoid testing for elements without titles
titleElement = $element.find('.u-field-title');
if (titleElement.length === 0) {
return;
}
fieldTitle = titleElement.text().trim();
if (!_.isUndefined(view.options.title) && !_.isUndefined(fieldTitle)) {
expect(fieldTitle).toBe(view.options.title);
}
if ('fieldValue' in view || 'imageUrl' in view) {
if ('imageUrl' in view) {
expect($($element.find('.image-frame')[0]).attr('src')).toBe(view.imageUrl());
} else if (view.fieldType === 'date') {
expect(view.fieldValue()).toBe(view.timezoneFormattedDate());
} else if (view.fieldValue()) {
expect(view.fieldValue()).toBe(view.modelValue());
} else if ('optionForValue' in view) {
expect($($element.find('.u-field-value .u-field-value-readonly')[0]).text()).toBe(
view.displayValue(view.modelValue())
);
} else {
expect($($element.find('.u-field-value .u-field-value-readonly')[0]).text()).toBe(view.modelValue());
}
} else {
throw new Error('Unexpected field type: ' + view.fieldType);
}
};
var expectProfilePrivacyFieldTobeRendered = function(learnerProfileView, othersProfile) {
var $accountPrivacyElement = $('.wrapper-profile-field-account-privacy');
var $privacyFieldElement = $($accountPrivacyElement).find('.u-field');
if (othersProfile) {
expect($privacyFieldElement.length).toBe(0);
} else {
expect($privacyFieldElement.length).toBe(1);
expectProfileElementContainsField($privacyFieldElement, learnerProfileView.options.accountPrivacyFieldView);
}
};
var expectSectionOneTobeRendered = function(learnerProfileView) {
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one'))
.find('.u-field, .social-links');
expect(sectionOneFieldElements.length).toBe(7);
expectProfileElementContainsField(sectionOneFieldElements[0], learnerProfileView.options.profileImageFieldView);
expectProfileElementContainsField(sectionOneFieldElements[1], learnerProfileView.options.usernameFieldView);
expectProfileElementContainsField(sectionOneFieldElements[2], learnerProfileView.options.nameFieldView);
_.each(_.rest(sectionOneFieldElements, 3), function(sectionFieldElement, fieldIndex) {
expectProfileElementContainsField(
sectionFieldElement,
learnerProfileView.options.sectionOneFieldViews[fieldIndex]
);
});
};
var expectSectionTwoTobeRendered = function(learnerProfileView) {
var $sectionTwoElement = $('.wrapper-profile-section-two');
var $sectionTwoFieldElements = $($sectionTwoElement).find('.u-field');
expect($sectionTwoFieldElements.length).toBe(learnerProfileView.options.sectionTwoFieldViews.length);
_.each($sectionTwoFieldElements, function(sectionFieldElement, fieldIndex) {
expectProfileElementContainsField(
sectionFieldElement,
learnerProfileView.options.sectionTwoFieldViews[fieldIndex]
);
});
};
var expectProfileSectionsAndFieldsToBeRendered = function(learnerProfileView, othersProfile) {
expectProfilePrivacyFieldTobeRendered(learnerProfileView, othersProfile);
expectSectionOneTobeRendered(learnerProfileView);
expectSectionTwoTobeRendered(learnerProfileView);
};
var expectLimitedProfileSectionsAndFieldsToBeRendered = function(learnerProfileView, othersProfile) {
var sectionOneFieldElements = $('.wrapper-profile-section-one').find('.u-field');
expectProfilePrivacyFieldTobeRendered(learnerProfileView, othersProfile);
expect(sectionOneFieldElements.length).toBe(2);
expectProfileElementContainsField(
sectionOneFieldElements[0],
learnerProfileView.options.profileImageFieldView
);
expectProfileElementContainsField(
sectionOneFieldElements[1],
learnerProfileView.options.usernameFieldView
);
if (othersProfile) {
expect($('.profile-private-message').text())
.toBe('This learner is currently sharing a limited profile.');
} else {
expect($('.profile-private-message').text()).toBe('You are currently sharing a limited profile.');
}
};
var expectProfileSectionsNotToBeRendered = function() {
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) {
// Unrelated initial request, no badge request
expect(requests.length).toBe(1);
expect(tabbedViewView).toBe(undefined);
};
var expectTabbedViewToBeShown = function(tabbedViewView) {
expect(tabbedViewView.$el.find('.page-content-nav').is(':visible')).toBe(true);
};
return {
expectLimitedProfileSectionsAndFieldsToBeRendered: expectLimitedProfileSectionsAndFieldsToBeRendered,
expectProfileSectionsAndFieldsToBeRendered: expectProfileSectionsAndFieldsToBeRendered,
expectProfileSectionsNotToBeRendered: expectProfileSectionsNotToBeRendered,
expectTabbedViewToBeUndefined: expectTabbedViewToBeUndefined,
expectTabbedViewToBeShown: expectTabbedViewToBeShown
};
});

View File

@@ -1,169 +0,0 @@
/* eslint-disable no-underscore-dangle */
(function(define) {
'use strict';
define([
'gettext',
'jquery',
'underscore',
'backbone',
'edx-ui-toolkit/js/utils/string-utils',
'edx-ui-toolkit/js/utils/html-utils',
'js/views/fields',
'js/views/image_field',
'text!learner_profile/templates/social_icons.underscore',
'backbone-super'
], function(gettext, $, _, Backbone, StringUtils, HtmlUtils, FieldViews, ImageFieldView, socialIconsTemplate) {
var LearnerProfileFieldViews = {};
LearnerProfileFieldViews.AccountPrivacyFieldView = FieldViews.DropdownFieldView.extend({
events: {
'click button.btn-change-privacy': 'finishEditing',
'change select': 'showSaveButton'
},
render: function() {
this._super();
this.showNotificationMessage();
this.updateFieldValue();
return this;
},
showNotificationMessage: function() {
var accountSettingsLink = HtmlUtils.joinHtml(
HtmlUtils.interpolateHtml(
HtmlUtils.HTML('<a href="{settings_url}">'), {settings_url: this.options.accountSettingsPageUrl}
),
gettext('Account Settings page.'),
HtmlUtils.HTML('</a>')
);
if (this.profileIsPrivate) {
this._super(
HtmlUtils.interpolateHtml(
gettext('You must specify your birth year before you can share your full profile. To specify your birth year, go to the {account_settings_page_link}'), // eslint-disable-line max-len
{account_settings_page_link: accountSettingsLink}
)
);
} else if (this.requiresParentalConsent) {
this._super(
HtmlUtils.interpolateHtml(
gettext('You must be over 13 to share a full profile. If you are over 13, make sure that you have specified a birth year on the {account_settings_page_link}'), // eslint-disable-line max-len
{account_settings_page_link: accountSettingsLink}
)
);
} else {
this._super('');
}
},
updateFieldValue: function() {
if (!this.isAboveMinimumAge) {
this.$('.u-field-value select').val('private');
this.disableField(true);
}
},
showSaveButton: function() {
$('.btn-change-privacy').removeClass('hidden');
}
});
LearnerProfileFieldViews.ProfileImageFieldView = ImageFieldView.extend({
screenReaderTitle: gettext('Profile Image'),
imageUrl: function() {
return this.model.profileImageUrl();
},
imageAltText: function() {
return StringUtils.interpolate(
gettext('Profile image for {username}'),
{username: this.model.get('username')}
);
},
imageChangeSucceeded: function() {
var view = this;
// Update model to get the latest urls of profile image.
this.model.fetch().done(function() {
view.setCurrentStatus('');
view.render();
view.$('.u-field-upload-button').focus();
}).fail(function() {
view.setCurrentStatus('');
view.showErrorMessage(view.errorMessage);
});
},
imageChangeFailed: function(e, data) {
this.setCurrentStatus('');
this.showImageChangeFailedMessage(data.jqXHR.status, data.jqXHR.responseText);
},
showImageChangeFailedMessage: function(status, responseText) {
var errors;
if (_.contains([400, 404], status)) {
try {
errors = JSON.parse(responseText);
this.showErrorMessage(errors.user_message);
} catch (error) {
this.showErrorMessage(this.errorMessage);
}
} else {
this.showErrorMessage(this.errorMessage);
}
},
showErrorMessage: function(message) {
this.options.messageView.showMessage(message);
},
isEditingAllowed: function() {
return this.model.isAboveMinimumAge();
},
isShowingPlaceholder: function() {
return !this.model.hasProfileImage();
},
clickedRemoveButton: function(e, data) {
this.options.messageView.hideMessage();
this._super(e, data);
},
fileSelected: function(e, data) {
this.options.messageView.hideMessage();
this._super(e, data);
}
});
LearnerProfileFieldViews.SocialLinkIconsView = Backbone.View.extend({
initialize: function(options) {
this.options = _.extend({}, options);
},
render: function() {
var socialLinks = {};
for (var platformName in this.options.socialPlatforms) { // eslint-disable-line no-restricted-syntax, guard-for-in, vars-on-top, max-len
socialLinks[platformName] = null;
for (var link in this.model.get('social_links')) { // eslint-disable-line no-restricted-syntax, vars-on-top, max-len
if (platformName === this.model.get('social_links')[link].platform) {
socialLinks[platformName] = this.model.get('social_links')[link].social_link;
}
}
}
HtmlUtils.setHtml(this.$el, HtmlUtils.template(socialIconsTemplate)({
socialLinks: socialLinks,
ownProfile: this.options.ownProfile
}));
return this;
}
});
return LearnerProfileFieldViews;
});
}).call(this, define || RequireJS.define);

View File

@@ -1,150 +0,0 @@
(function(define) {
'use strict';
define(
[
'gettext', 'jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/html-utils',
'common/js/components/views/tabbed_view',
'learner_profile/js/views/section_two_tab'
],
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab) {
var LearnerProfileView = Backbone.View.extend({
initialize: function(options) {
var Router;
this.options = _.extend({}, options);
_.bindAll(this, 'showFullProfile', 'render', 'renderFields', 'showLoadingError');
this.listenTo(this.options.preferencesModel, 'change:account_privacy', this.render);
Router = Backbone.Router.extend({
routes: {':about_me': 'loadTab', ':accomplishments': 'loadTab'}
});
this.router = new Router();
this.firstRender = true;
},
showFullProfile: function() {
var isAboveMinimumAge = this.options.accountSettingsModel.isAboveMinimumAge();
if (this.options.ownProfile) {
return isAboveMinimumAge
&& this.options.preferencesModel.get('account_privacy') === 'all_users';
} else {
return this.options.accountSettingsModel.get('profile_is_public');
}
},
setActiveTab: function(tab) {
// This tab may not actually exist.
if (this.tabbedView.getTabMeta(tab).tab) {
this.tabbedView.setActiveTab(tab);
}
},
render: function() {
var tabs,
$tabbedViewElement,
$wrapperProfileBioElement = this.$el.find('.wrapper-profile-bio'),
self = this;
this.sectionTwoView = new SectionTwoTab({
viewList: this.options.sectionTwoFieldViews,
showFullProfile: this.showFullProfile,
ownProfile: this.options.ownProfile
});
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()) {
tabs = [
{view: this.sectionTwoView, title: gettext('About Me'), url: 'about_me'}
];
this.tabbedView = new TabbedView({
tabs: tabs,
router: this.router,
viewLabel: gettext('Profile')
});
$tabbedViewElement = this.tabbedView.render().el;
HtmlUtils.setHtml(
$wrapperProfileBioElement,
HtmlUtils.HTML($tabbedViewElement)
);
if (this.firstRender) {
this.router.on('route:loadTab', _.bind(this.setActiveTab, this));
Backbone.history.start();
this.firstRender = false;
// Load from history.
this.router.navigate((Backbone.history.getFragment() || 'about_me'), {trigger: true});
} else {
// Restart the router so the tab will be brought up anew.
Backbone.history.stop();
Backbone.history.start();
}
} else {
if (this.isCoppaCompliant()) {
// xss-lint: disable=javascript-jquery-html
$wrapperProfileBioElement.html(this.sectionTwoView.render().el);
}
}
return this;
},
isCoppaCompliant: function() {
var enableCoppaCompliance = this.options.accountSettingsModel.get('enable_coppa_compliance'),
isAboveAge = this.options.accountSettingsModel.isAboveMinimumAge();
return !enableCoppaCompliance || (enableCoppaCompliance && isAboveAge);
},
renderFields: function() {
var view = this,
fieldView,
imageView,
settings;
if (this.options.ownProfile && this.isCoppaCompliant()) {
fieldView = this.options.accountPrivacyFieldView;
settings = this.options.accountSettingsModel;
fieldView.profileIsPrivate = !settings.get('year_of_birth');
fieldView.requiresParentalConsent = settings.get('requires_parental_consent');
fieldView.isAboveMinimumAge = settings.isAboveMinimumAge();
fieldView.undelegateEvents();
this.$('.wrapper-profile-field-account-privacy').prepend(fieldView.render().el);
fieldView.delegateEvents();
}
// Clear existing content in user profile card
this.$('.profile-section-one-fields').html('');
// Do not show name when in limited mode or no name has been set
if (this.showFullProfile() && this.options.accountSettingsModel.get('name')) {
this.$('.profile-section-one-fields').append(this.options.nameFieldView.render().el);
}
this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el);
imageView = this.options.profileImageFieldView;
this.$('.profile-image-field').append(imageView.render().el);
if (this.showFullProfile()) {
_.each(this.options.sectionOneFieldViews, function(childFieldView) {
view.$('.profile-section-one-fields').append(childFieldView.render().el);
});
}
},
showLoadingError: function() {
this.$('.ui-loading-indicator').addClass('is-hidden');
this.$('.ui-loading-error').removeClass('is-hidden');
}
});
return LearnerProfileView;
});
}).call(this, define || RequireJS.define);

View File

@@ -1,33 +0,0 @@
(function(define) {
'use strict';
define(
[
'gettext', 'jquery', 'underscore', 'backbone', 'text!learner_profile/templates/section_two.underscore',
'edx-ui-toolkit/js/utils/html-utils'
],
function(gettext, $, _, Backbone, sectionTwoTemplate, HtmlUtils) {
var SectionTwoTab = Backbone.View.extend({
attributes: {
class: 'wrapper-profile-section-two'
},
template: _.template(sectionTwoTemplate),
initialize: function(options) {
this.options = _.extend({}, options);
},
render: function() {
var self = this;
var showFullProfile = this.options.showFullProfile();
this.$el.html(HtmlUtils.HTML(this.template({ownProfile: self.options.ownProfile, showFullProfile: showFullProfile})).toString()); // eslint-disable-line max-len
if (showFullProfile) {
_.each(this.options.viewList, function(fieldView) {
self.$el.find('.field-container').append(fieldView.render().el);
});
}
return this;
}
});
return SectionTwoTab;
});
}).call(this, define || RequireJS.define);

View File

@@ -1,10 +0,0 @@
<div class="profile-section-two-fields">
<div class="field-container"></div>
<% if (!showFullProfile) { %>
<% if(ownProfile) { %>
<span class="profile-private-message"><%- gettext("You are currently sharing a limited profile.") %></span>
<% } else { %>
<span class="profile-private-message"><%- gettext("This learner is currently sharing a limited profile.") %></span>
<% } %>
<% } %>
</div>

View File

@@ -1,9 +0,0 @@
<div class="social-links">
<% for (var platform in socialLinks) { %>
<% if (socialLinks[platform]) { %>
<a rel="noopener" target="_blank" href= <%-socialLinks[platform]%>>
<span class="icon fa fa-<%-platform%>-square" data-platform=<%-platform%> aria-hidden="true"></span>
</a>
<% } %>
<% } %>
</div>

View File

@@ -1,47 +0,0 @@
<%page expression_filter="h"/>
<%!
from django.utils.translation import gettext as _
from common.djangoapps.third_party_auth import pipeline
%>
<li class="controls--account">
<span class="title">
## Translators: this section lists all the third-party authentication providers (for example, Google and LinkedIn) the user can link with or unlink from their edX account.
${_("Connected Accounts")}
</span>
<span class="data">
<span class="third-party-auth">
% for state in provider_user_states:
<div class="auth-provider">
<div class="status">
% if state.has_account:
<span class="icon fa fa-link" aria-hidden="true"></span> <span class="copy">${_('Linked')}</span>
% else:
<span class="icon fa fa-unlink" aria-hidden="true"></span><span class="copy">${_('Not Linked')}</span>
% endif
</div>
<span class="provider">${state.provider.name}</span>
<span class="control">
<form
action="${pipeline.get_disconnect_url(state.provider.provider_id, state.association_id)}"
method="post"
name="${state.get_unlink_form_name()}">
% if state.has_account:
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}">
<button type="button" onclick="document.${state.get_unlink_form_name()}.submit()">
## Translators: clicking on this removes the link between a user's edX account and their account with an external authentication provider (like Google or LinkedIn).
${_("Unlink")}
</button>
% elif state.provider.display_for_login:
<a href="${pipeline.get_login_url(state.provider.provider_id, pipeline.AUTH_ENTRY_PROFILE)}">
## Translators: clicking on this creates a link between a user's edX account and their account with an external authentication provider (like Google or LinkedIn).
${_("Link")}
</a>
% endif
</form>
</span>
</div>
% endfor
</span>
</li>

View File

@@ -1,69 +0,0 @@
## mako
<%page expression_filter="h"/>
<%namespace name='static' file='/static_content.html'/>
<%!
from django.utils.translation import gettext 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:
<%
course = certificate['course']
completion_date_message_html = Text(_('Completed {completion_date_html}')).format(
completion_date_html=HTML(
'<span'
' class="localized-datetime start-date"'
' data-datetime="{completion_date}"'
' 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,
),
)
%>
<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>
% endfor
% elif own_profile:
<div class="learner-message">
<h4 class="message-header">${_("You haven't earned any certificates yet.")}</h4>
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
<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>
% endif
</div>
% endif
% endif
</div>
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform('.localized-datetime');
</%static:require_module_async>

View File

@@ -1,79 +0,0 @@
## mako
<%page expression_filter="h"/>
<%inherit file="/main.html" />
<%def name="online_help_token()"><% return "profile" %></%def>
<%namespace name='static' file='/static_content.html'/>
<%!
import json
from django.urls import reverse
from django.utils.translation import gettext 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="profile ${'profile-self' if own_profile else 'profile-other'}">
<div class="wrapper-profile-field-account-privacy">
% if own_profile and records_url:
<div class="wrapper-profile-records">
<a href="${records_url}">
<button class="btn profile-records-button">${_("View My Records")}</button>
</a>
</div>
% endif
</div>
% if own_profile:
<div class="profile-header">
<h2 class="header">${_("My Profile")}</h2>
<div class="subheader">
${_('Build out your profile to personalize your identity on {platform_name}.').format(
platform_name=platform_name,
)}
</div>
</div>
% endif
<div class="wrapper-profile-sections account-settings-container">
<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">
<div class="wrapper-profile-bio"></div>
% if achievements_fragment:
${HTML(achievements_fragment.body_html())}
% endif
</div>
</div>
</div>
</div>
</main>
<%block name="js_extra">
<%static:require_module module_name="learner_profile/js/learner_profile_factory" class_name="LearnerProfileFactory">
var options = ${data | n, dump_js_escaped_json};
LearnerProfileFactory(options);
</%static:require_module>
</%block>

View File

@@ -1,281 +0,0 @@
""" Tests for student profile views. """
import datetime
from unittest import mock
import ddt
from django.conf import settings
from django.test.client import RequestFactory
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from opaque_keys.edx.locator import CourseLocator
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from common.djangoapps.util.testing import UrlResetMixin
from lms.djangoapps.certificates.data import CertificateStatuses
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
from lms.envs.test import CREDENTIALS_PUBLIC_SERVICE_URL
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
from openedx.features.learner_profile.toggles import REDIRECT_TO_PROFILE_MICROFRONTEND
from openedx.features.learner_profile.views.learner_profile import learner_profile_context
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
@ddt.ddt
class LearnerProfileViewTest(SiteMixin, 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',
'preferences_api_url',
'account_settings_page_url',
'has_preferences_access',
'own_profile',
'country_options',
'language_options',
'account_settings_data',
'preferences_data',
]
def setUp(self):
super().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):
"""
Verify learner profile page context data.
"""
request = RequestFactory().get('/url')
request.user = self.user
context = learner_profile_context(request, self.USERNAME, self.user.is_staff)
assert context['data']['default_public_account_fields'] == \
settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields']
assert context['data']['accounts_api_url'] == \
reverse('accounts_api', kwargs={'username': self.user.username})
assert context['data']['preferences_api_url'] == \
reverse('preferences_api', kwargs={'username': self.user.username})
assert context['data']['profile_image_upload_url'] == \
reverse('profile_image_upload', kwargs={'username': self.user.username})
assert context['data']['profile_image_remove_url'] == \
reverse('profile_image_remove', kwargs={'username': self.user.username})
assert context['data']['profile_image_max_bytes'] == settings.PROFILE_IMAGE_MAX_BYTES
assert context['data']['profile_image_min_bytes'] == settings.PROFILE_IMAGE_MIN_BYTES
assert context['data']['account_settings_page_url'] == reverse('account_settings')
for attribute in self.CONTEXT_DATA:
assert attribute in context['data']
def test_view(self):
"""
Verify learner profile page view.
"""
profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME})
response = self.client.get(path=profile_path)
for attribute in self.CONTEXT_DATA:
self.assertContains(response, attribute)
def test_redirect_view(self):
with override_waffle_flag(REDIRECT_TO_PROFILE_MICROFRONTEND, active=True):
profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME})
# Test with waffle flag active and site setting disabled, does not redirect
response = self.client.get(path=profile_path)
for attribute in self.CONTEXT_DATA:
self.assertContains(response, attribute)
# Test with waffle flag active and site setting enabled, redirects to microfrontend
site_domain = 'othersite.example.com'
self.set_up_site(site_domain, {
'SITE_NAME': site_domain,
'ENABLE_PROFILE_MICROFRONTEND': True
})
self.client.login(username=self.USERNAME, password=self.PASSWORD)
response = self.client.get(path=profile_path)
profile_url = settings.PROFILE_MICROFRONTEND_URL
self.assertRedirects(response, profile_url + self.USERNAME, fetch_redirect_response=False)
def test_records_link(self):
profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME})
response = self.client.get(path=profile_path)
self.assertContains(response, f'<a href="{CREDENTIALS_PUBLIC_SERVICE_URL}/records/">')
def test_undefined_profile_page(self):
"""
Verify that a 404 is returned for a non-existent profile page.
"""
profile_path = reverse('learner_profile', kwargs={'username': "no_such_user"})
response = self.client.get(path=profile_path)
assert 404 == response.status_code
def _create_certificate(self, course_key=None, enrollment_mode=CourseMode.HONOR, status='downloadable'):
"""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=course_key or self.course.id,
mode=enrollment_mode,
download_url=self.DOWNLOAD_URL,
status=status,
)
@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(enrollment_mode=cert_mode)
cert.save()
response = self.client.get(f'/u/{self.user.username}')
self.assertContains(response, f'card certificate-card mode-{cert_mode}')
@ddt.data(
['downloadable', True],
['notpassing', False],
)
@ddt.unpack
def test_certificate_status_visibility(self, status, is_passed_status):
"""
Verify that certificates are only displayed for passing status.
"""
# Add new certificate
cert = self._create_certificate(status=status)
cert.save()
# Ensure that this test is actually using both passing and non-passing certs.
assert CertificateStatuses.is_passing_status(cert.status) == is_passed_status
response = self.client.get(f'/u/{self.user.username}')
if is_passed_status:
self.assertContains(response, f'card certificate-card mode-{cert.mode}')
else:
self.assertNotContains(response, f'card certificate-card mode-{cert.mode}')
def test_certificate_for_missing_course(self):
"""
Verify that a certificate is not shown for a missing course.
"""
# Add new certificate
cert = self._create_certificate(course_key=CourseLocator.from_string('course-v1:edX+INVALID+1'))
cert.save()
response = self.client.get(f'/u/{self.user.username}')
self.assertNotContains(response, f'card certificate-card mode-{cert.mode}')
@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.
"""
profile_username = self.user.username if own_profile else self.other_user.username
response = self.client.get(f'/u/{profile_username}')
if own_profile:
self.assertContains(response, 'You haven&#39;t earned any certificates yet.')
else:
self.assertNotContains(response, 'You haven&#39;t earned any certificates yet.')
@ddt.data(True, False)
def test_explore_courses_visibility(self, courses_browsable):
with mock.patch.dict('django.conf.settings.FEATURES', {'COURSES_ARE_BROWSABLE': courses_browsable}):
response = self.client.get(f'/u/{self.user.username}')
if courses_browsable:
self.assertContains(response, 'Explore New Courses')
else:
self.assertNotContains(response, 'Explore New Courses')
def test_certificate_for_visibility_for_not_viewable_course(self):
"""
Verify that a certificate is not shown if certificate are not viewable to users.
"""
# add new course with certificate_available_date is future date.
course = CourseFactory.create(
certificate_available_date=datetime.datetime.now() + datetime.timedelta(days=5),
certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
)
cert = self._create_certificate(course_key=course.id)
cert.save()
response = self.client.get(f'/u/{self.user.username}')
self.assertNotContains(response, f'card certificate-card mode-{cert.mode}')
def test_certificates_visible_only_for_staff_and_profile_user(self):
"""
Verify that certificates data are passed to template only in case of staff user
and profile user.
"""
request = RequestFactory().get('/url')
request.user = self.user
profile_username = self.other_user.username
user_is_staff = True
context = learner_profile_context(request, profile_username, user_is_staff)
assert 'achievements_fragment' in context
user_is_staff = False
context = learner_profile_context(request, profile_username, user_is_staff)
assert 'achievements_fragment' not in context
profile_username = self.user.username
context = learner_profile_context(request, profile_username, user_is_staff)
assert 'achievements_fragment' in context
@mock.patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
def test_certificate_visibility_with_no_cert_config(self):
"""
Verify that certificates are not displayed until there is an active
certificate configuration.
"""
# Add new certificate
cert = self._create_certificate(enrollment_mode=CourseMode.VERIFIED)
cert.download_url = ''
cert.save()
response = self.client.get(f'/u/{self.user.username}')
self.assertNotContains(
response, f'card certificate-card mode-{CourseMode.VERIFIED}'
)
course_overview = CourseOverview.get_from_id(self.course.id)
course_overview.has_any_active_web_certificate = True
course_overview.save()
response = self.client.get(f'/u/{self.user.username}')
self.assertContains(
response, f'card certificate-card mode-{CourseMode.VERIFIED}'
)

View File

@@ -1,29 +0,0 @@
"""
Toggles for Learner Profile page.
"""
from edx_toggles.toggles import WaffleFlag
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
# Namespace for learner profile waffle flags.
WAFFLE_FLAG_NAMESPACE = 'learner_profile'
# Waffle flag to redirect to another learner profile experience.
# .. toggle_name: learner_profile.redirect_to_microfrontend
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the profile page.
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2019-02-19
# .. toggle_target_removal_date: 2020-12-31
# .. toggle_warning: Also set settings.PROFILE_MICROFRONTEND_URL and site's ENABLE_PROFILE_MICROFRONTEND.
# .. toggle_tickets: DEPR-17
REDIRECT_TO_PROFILE_MICROFRONTEND = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.redirect_to_microfrontend', __name__)
def should_redirect_to_profile_microfrontend():
return (
configuration_helpers.get_value('ENABLE_PROFILE_MICROFRONTEND') and
REDIRECT_TO_PROFILE_MICROFRONTEND.is_enabled()
)

View File

@@ -1,24 +0,0 @@
"""
Defines URLs for the learner profile.
"""
from django.conf import settings
from django.urls import path, re_path
from openedx.features.learner_profile.views.learner_profile import learner_profile
from .views.learner_achievements import LearnerAchievementsFragmentView
urlpatterns = [
re_path(
r'^{username_pattern}$'.format(
username_pattern=settings.USERNAME_PATTERN,
),
learner_profile,
name='learner_profile',
),
path('achievements', LearnerAchievementsFragmentView.as_view(),
name='openedx.learner_profile.learner_achievements_fragment_view',
),
]

View File

@@ -1,58 +0,0 @@
"""
Views to render a learner's achievements.
"""
from django.template.loader import render_to_string
from web_fragments.fragment import Fragment
from lms.djangoapps.certificates import api as certificate_api
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
class LearnerAchievementsFragmentView(EdxFragmentView):
"""
A fragment to render a learner's achievements.
"""
def render_to_fragment(self, request, username=None, own_profile=False, **kwargs): # lint-amnesty, pylint: disable=arguments-differ
"""
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)
passing_certificates = []
for course_certificate in course_certificates:
if course_certificate.get('is_passing', False):
course_key = course_certificate['course_key']
try:
course_overview = CourseOverview.get_from_id(course_key)
course_certificate['course'] = course_overview
if certificate_api.certificates_viewable_for_course(course_overview):
# add certificate into passing certificate list only if it's a PDF certificate
# or there is an active certificate configuration.
if course_certificate['is_pdf_certificate'] or course_overview.has_any_active_web_certificate:
passing_certificates.append(course_certificate)
except CourseOverview.DoesNotExist:
# This is unlikely to fail as the course should exist.
# Ideally the cert should have all the information that
# it needs. This might be solved by the Credentials API.
pass
passing_certificates.sort(key=lambda certificate: certificate['course'].display_name_with_default)
return passing_certificates

View File

@@ -1,128 +0,0 @@
""" Views for a student's profile information. """
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404
from django.shortcuts import redirect, render
from django.urls import reverse
from django.views.decorators.http import require_http_methods
from django_countries import countries
from common.djangoapps.edxmako.shortcuts import marketing_link
from openedx.core.djangoapps.credentials.utils import get_credentials_records_url
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
from openedx.core.djangoapps.user_api.errors import UserNotAuthorized, UserNotFound
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
from openedx.features.learner_profile.toggles import should_redirect_to_profile_microfrontend
from openedx.features.learner_profile.views.learner_achievements import LearnerAchievementsFragmentView
from common.djangoapps.student.models import User
@login_required
@require_http_methods(['GET'])
def learner_profile(request, username):
"""Render the profile page for the specified username.
Args:
request (HttpRequest)
username (str): username of user whose profile is requested.
Returns:
HttpResponse: 200 if the page was sent successfully
HttpResponse: 302 if not logged in (redirect to login page)
HttpResponse: 405 if using an unsupported HTTP method
Raises:
Http404: 404 if the specified user is not authorized or does not exist
Example usage:
GET /account/profile
"""
if should_redirect_to_profile_microfrontend():
profile_microfrontend_url = f"{settings.PROFILE_MICROFRONTEND_URL}{username}"
if request.GET:
profile_microfrontend_url += f'?{request.GET.urlencode()}'
return redirect(profile_microfrontend_url)
try:
context = learner_profile_context(request, username, request.user.is_staff)
return render(
request=request,
template_name='learner_profile/learner_profile.html',
context=context
)
except (UserNotAuthorized, UserNotFound, ObjectDoesNotExist):
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
def learner_profile_context(request, profile_username, user_is_staff):
"""Context for the learner profile page.
Args:
logged_in_user (object): Logged In user.
profile_username (str): username of user whose profile is requested.
user_is_staff (bool): Logged In user has staff access.
build_absolute_uri_func ():
Returns:
dict
Raises:
ObjectDoesNotExist: the specified profile_username does not exist.
"""
profile_user = User.objects.get(username=profile_username)
logged_in_user = request.user
own_profile = (logged_in_user.username == profile_username)
account_settings_data = get_account_settings(request, [profile_username])[0]
preferences_data = get_user_preferences(profile_user, profile_username)
context = {
'own_profile': own_profile,
'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'],
'default_visibility': settings.ACCOUNT_VISIBILITY_CONFIGURATION['default_visibility'],
'accounts_api_url': reverse("accounts_api", kwargs={'username': profile_username}),
'preferences_api_url': reverse('preferences_api', kwargs={'username': profile_username}),
'preferences_data': preferences_data,
'account_settings_data': account_settings_data,
'profile_image_upload_url': reverse('profile_image_upload', kwargs={'username': profile_username}),
'profile_image_remove_url': reverse('profile_image_remove', kwargs={'username': profile_username}),
'profile_image_max_bytes': settings.PROFILE_IMAGE_MAX_BYTES,
'profile_image_min_bytes': settings.PROFILE_IMAGE_MIN_BYTES,
'account_settings_page_url': reverse('account_settings'),
'has_preferences_access': (logged_in_user.username == profile_username or user_is_staff),
'own_profile': own_profile,
'country_options': list(countries),
'find_courses_url': marketing_link('COURSES'),
'language_options': settings.ALL_LANGUAGES,
'backpack_ui_img': staticfiles_storage.url('certificates/images/backpack-ui.png'),
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
'social_platforms': settings.SOCIAL_PLATFORMS,
'enable_coppa_compliance': settings.ENABLE_COPPA_COMPLIANCE,
'parental_consent_age_limit': settings.PARENTAL_CONSENT_AGE_LIMIT
},
'show_program_listing': ProgramsApiConfig.is_enabled(),
'show_dashboard_tabs': True,
'disable_courseware_js': True,
'nav_hidden': True,
'records_url': get_credentials_records_url(),
}
if own_profile or user_is_staff:
achievements_fragment = LearnerAchievementsFragmentView().render_to_fragment(
request,
username=profile_user.username,
own_profile=own_profile,
)
context['achievements_fragment'] = achievements_fragment
return context