Unverified Commit dd4d13af by Bill Filler Committed by GitHub

Merge pull request #16611 from edx/afzaledx/WL-1219

Added extended profile fields to the Account settings page.
parents 773b622e f589dc9d
......@@ -2,9 +2,9 @@
import json
import logging
import urlparse
from datetime import datetime
import urlparse
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
......@@ -16,7 +16,6 @@ from django.utils.translation import ugettext as _
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from django_countries import countries
import third_party_auth
from edxmako.shortcuts import render_to_response
from lms.djangoapps.commerce.models import CommerceConfiguration
......@@ -403,6 +402,65 @@ def _get_form_descriptions(request):
}
def _get_extended_profile_fields():
"""Retrieve the extended profile fields from site configuration to be shown on the
Account Settings page
Returns:
A list of dicts. Each dict corresponds to a single field. The keys per field are:
"field_name" : name of the field stored in user_profile.meta
"field_label" : The label of the field.
"field_type" : TextField or ListField
"field_options": a list of tuples for options in the dropdown in case of ListField
"""
extended_profile_fields = []
fields_already_showing = ['username', 'name', 'email', 'pref-lang', 'country', 'time_zone', 'level_of_education',
'gender', 'year_of_birth', 'language_proficiencies', 'social_links']
field_labels_map = {
"first_name": _(u"First Name"),
"last_name": _(u"Last Name"),
"city": _(u"City"),
"state": _(u"State/Province/Region"),
"company": _(u"Company"),
"title": _(u"Title"),
"mailing_address": _(u"Mailing address"),
"goals": _(u"Tell us why you're interested in {platform_name}").format(
platform_name=configuration_helpers.get_value("PLATFORM_NAME", settings.PLATFORM_NAME)
),
"profession": _("Profession"),
"specialty": _("Specialty")
}
extended_profile_field_names = configuration_helpers.get_value('extended_profile_fields', [])
for field_to_exclude in fields_already_showing:
if field_to_exclude in extended_profile_field_names:
extended_profile_field_names.remove(field_to_exclude) # pylint: disable=no-member
extended_profile_field_options = configuration_helpers.get_value('EXTRA_FIELD_OPTIONS', [])
extended_profile_field_option_tuples = {}
for field in extended_profile_field_options.keys():
field_options = extended_profile_field_options[field]
extended_profile_field_option_tuples[field] = [(option.lower(), option) for option in field_options]
for field in extended_profile_field_names:
field_dict = {
"field_name": field,
"field_label": field_labels_map.get(field, field),
}
field_options = extended_profile_field_option_tuples.get(field)
if field_options:
field_dict["field_type"] = "ListField"
field_dict["field_options"] = field_options
else:
field_dict["field_type"] = "TextField"
extended_profile_fields.append(field_dict)
return extended_profile_fields
def _external_auth_intercept(request, mode):
"""Allow external auth to intercept a login/registration request.
......@@ -564,7 +622,8 @@ def account_settings_context(request):
'disable_courseware_js': True,
'show_program_listing': ProgramsApiConfig.is_enabled(),
'show_dashboard_tabs': True,
'order_history': user_orders
'order_history': user_orders,
'extended_profile_fields': _get_extended_profile_fields(),
}
enterprise_customer_name = None
......
......@@ -3096,6 +3096,7 @@ ACCOUNT_VISIBILITY_CONFIGURATION = {
"requires_parental_consent",
"account_privacy",
"accomplishments_shared",
"extended_profile",
]
}
......
......@@ -24,7 +24,8 @@
requires_parental_consent: true,
profile_image: null,
accomplishments_shared: false,
default_public_account_fields: []
default_public_account_fields: [],
extended_profile: []
},
parse: function(response) {
......
......@@ -26,14 +26,15 @@
syncLearnerProfileData,
enterpriseName,
enterpriseReadonlyAccountFields,
edxSupportUrl
edxSupportUrl,
extendedProfileFields
) {
var $accountSettingsElement, userAccountModel, userPreferencesModel, aboutSectionsData,
accountsSectionData, ordersSectionData, accountSettingsView, showAccountSettingsPage,
showLoadingError, orderNumber, getUserField, userFields, timeZoneDropdownField, countryDropdownField,
emailFieldView, socialFields, platformData,
aboutSectionMessageType, aboutSectionMessage, fullnameFieldView, countryFieldView,
fullNameFieldData, emailFieldData, countryFieldData;
fullNameFieldData, emailFieldData, countryFieldData, additionalFields, fieldItem;
$accountSettingsElement = $('.wrapper-account-settings');
......@@ -231,6 +232,37 @@
}
];
// Add the extended profile fields
additionalFields = aboutSectionsData[1];
for (var field in extendedProfileFields) { // eslint-disable-line guard-for-in, no-restricted-syntax, vars-on-top, max-len
fieldItem = extendedProfileFields[field];
if (fieldItem.field_type === 'TextField') {
additionalFields.fields.push({
view: new AccountSettingsFieldViews.ExtendedFieldTextFieldView({
model: userAccountModel,
title: fieldItem.field_label,
fieldName: fieldItem.field_name,
valueAttribute: 'extended_profile',
persistChanges: true
})
});
} else {
if (fieldItem.field_type === 'ListField') {
additionalFields.fields.push({
view: new AccountSettingsFieldViews.ExtendedFieldListFieldView({
model: userAccountModel,
title: fieldItem.field_label,
fieldName: fieldItem.field_name,
options: fieldItem.field_options,
valueAttribute: 'extended_profile',
persistChanges: true
})
});
}
}
}
// Add the social link fields
socialFields = {
title: gettext('Social Media Links'),
......
......@@ -217,9 +217,10 @@
}
},
saveValue: function() {
var attributes = {},
value = '';
if (this.persistChanges === true) {
var attributes = {},
value = this.fieldValue() ? [{code: this.fieldValue()}] : [];
value = this.fieldValue() ? [{code: this.fieldValue()}] : [];
attributes[this.options.valueAttribute] = value;
this.saveAttributes(attributes);
}
......@@ -258,6 +259,61 @@
}
}
}),
ExtendedFieldTextFieldView: FieldViews.TextFieldView.extend({
render: function() {
HtmlUtils.setHtml(this.$el, HtmlUtils.template(field_text_account_template)({
id: this.options.valueAttribute + '_' + this.options.field_name,
title: this.options.title,
value: this.modelValue(),
message: this.options.helpMessage,
placeholder: this.options.placeholder || ''
}));
this.delegateEvents();
return this;
},
modelValue: function() {
var extendedProfileFields = this.model.get(this.options.valueAttribute);
for (var i = 0; i < extendedProfileFields.length; i++) { // eslint-disable-line vars-on-top
if (extendedProfileFields[i].field_name === this.options.fieldName) {
return extendedProfileFields[i].field_value;
}
}
return null;
},
saveValue: function() {
var attributes, value;
if (this.persistChanges === true) {
attributes = {};
value = this.fieldValue() != null ? [{field_name: this.options.fieldName,
field_value: this.fieldValue()}] : [];
attributes[this.options.valueAttribute] = value;
this.saveAttributes(attributes);
}
}
}),
ExtendedFieldListFieldView: FieldViews.DropdownFieldView.extend({
fieldTemplate: field_dropdown_account_template,
modelValue: function() {
var extendedProfileFields = this.model.get(this.options.valueAttribute);
for (var i = 0; i < extendedProfileFields.length; i++) { // eslint-disable-line vars-on-top
if (extendedProfileFields[i].field_name === this.options.fieldName) {
return extendedProfileFields[i].field_value;
}
}
return null;
},
saveValue: function() {
var attributes = {},
value;
if (this.persistChanges === true) {
value = this.fieldValue() ? [{field_name: this.options.fieldName,
field_value: this.fieldValue()}] : [];
attributes[this.options.valueAttribute] = value;
this.saveAttributes(attributes);
}
}
}),
AuthFieldView: FieldViews.LinkFieldView.extend({
fieldTemplate: field_social_link_template,
className: function() {
......
......@@ -43,7 +43,8 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
syncLearnerProfileData = ${ bool(sync_learner_profile_data) | n, dump_js_escaped_json },
enterpriseName = '${ enterprise_name | n, js_escaped_string }',
enterpriseReadonlyAccountFields = ${ enterprise_readonly_account_fields | n, dump_js_escaped_json },
edxSupportUrl = '${ edx_support_url | n, js_escaped_string }';
edxSupportUrl = '${ edx_support_url | n, js_escaped_string }',
extendedProfileFields = ${ extended_profile_fields | n, dump_js_escaped_json };
AccountSettingsFactory(
fieldsData,
......@@ -61,7 +62,8 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
syncLearnerProfileData,
enterpriseName,
enterpriseReadonlyAccountFields,
edxSupportUrl
edxSupportUrl,
extendedProfileFields
);
</%static:require_module>
</%block>
......@@ -229,6 +229,17 @@ def update_account_settings(requesting_user, update, username=None):
existing_user_profile.set_meta(meta)
existing_user_profile.save()
# updating extended user profile info
if 'extended_profile' in update:
meta = existing_user_profile.get_meta()
new_extended_profile = update['extended_profile']
for field in new_extended_profile:
field_name = field['field_name']
new_value = field['field_value']
meta[field_name] = new_value
existing_user_profile.set_meta(meta)
existing_user_profile.save()
except PreferenceValidationError as err:
raise AccountValidationError(err.preference_errors)
except AccountValidationError as err:
......
"""
Django REST Framework serializers for the User API Accounts sub-application
"""
import json
import logging
from rest_framework import serializers
......@@ -10,6 +11,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from lms.djangoapps.badges.utils import badges_enabled
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api import errors
from openedx.core.djangoapps.user_api.models import UserPreference
from openedx.core.djangoapps.user_api.serializers import ReadOnlyFieldsSerializerMixin
......@@ -112,6 +114,7 @@ class UserReadOnlySerializer(serializers.Serializer):
"accomplishments_shared": accomplishments_shared,
"account_privacy": self.configuration.get('default_visibility'),
"social_links": None,
"extended_profile_fields": None,
}
if user_profile:
......@@ -138,6 +141,7 @@ class UserReadOnlySerializer(serializers.Serializer):
"social_links": SocialLinkSerializer(
user_profile.social_links.all(), many=True
).data,
"extended_profile": get_extended_profile(user_profile),
}
)
......@@ -327,6 +331,26 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
return instance
def get_extended_profile(user_profile):
"""Returns the extended user profile fields stored in user_profile.meta"""
# pick the keys from the site configuration
extended_profile_field_names = configuration_helpers.get_value('extended_profile_fields', [])
try:
extended_profile_fields_data = json.loads(user_profile.meta)
except ValueError:
extended_profile_fields_data = {}
extended_profile = []
for field_name in extended_profile_field_names:
extended_profile.append({
"field_name": field_name,
"field_value": extended_profile_fields_data.get(field_name, "")
})
return extended_profile
def get_profile_visibility(user_profile, user, configuration=None):
"""Returns the visibility level for the specified user profile."""
if user_profile.requires_parental_consent():
......
......@@ -318,6 +318,7 @@ class AccountSettingsOnCreationTest(TestCase):
'language_proficiencies': [],
'account_privacy': PRIVATE_VISIBILITY,
'accomplishments_shared': False,
'extended_profile': [],
})
......
......@@ -250,7 +250,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
Verify that all account fields are returned (even those that are not shareable).
"""
data = response.data
self.assertEqual(18, len(data))
self.assertEqual(19, len(data))
self.assertEqual(self.user.username, data["username"])
self.assertEqual(self.user.first_name + " " + self.user.last_name, data["name"])
self.assertEqual("US", data["country"])
......@@ -382,7 +382,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
with self.assertNumQueries(queries):
response = self.send_get(self.client)
data = response.data
self.assertEqual(18, len(data))
self.assertEqual(19, len(data))
self.assertEqual(self.user.username, data["username"])
self.assertEqual(self.user.first_name + " " + self.user.last_name, data["name"])
for empty_field in ("year_of_birth", "level_of_education", "mailing_address", "bio"):
......@@ -776,7 +776,7 @@ class TestAccountsAPI(CacheIsolationTestCase, UserAPITestCase):
response = self.send_get(client)
if has_full_access:
data = response.data
self.assertEqual(18, len(data))
self.assertEqual(19, len(data))
self.assertEqual(self.user.username, data["username"])
self.assertEqual(self.user.first_name + " " + self.user.last_name, data["name"])
self.assertEqual(self.user.email, data["email"])
......
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