diff --git a/common/test/acceptance/pages/lms/dashboard.py b/common/test/acceptance/pages/lms/dashboard.py
index 10958a34d9..29a20e1b05 100644
--- a/common/test/acceptance/pages/lms/dashboard.py
+++ b/common/test/acceptance/pages/lms/dashboard.py
@@ -202,3 +202,9 @@ class DashboardPage(PageObject):
Click on `Account Settings` link.
"""
self.q(css='.dropdown-menu li a').first.click()
+
+ def click_my_profile_link(self):
+ """
+ Click on `My Profile` link.
+ """
+ self.q(css='.dropdown-menu li a').nth(1).click()
diff --git a/common/test/acceptance/pages/lms/fields.py b/common/test/acceptance/pages/lms/fields.py
index 62124ea4f1..7512937b84 100644
--- a/common/test/acceptance/pages/lms/fields.py
+++ b/common/test/acceptance/pages/lms/fields.py
@@ -30,6 +30,40 @@ class FieldsMixin(object):
"Field with id \"{0}\" is in DOM.".format(field_id)
).fulfill()
+ def mode_for_field(self, field_id):
+ """
+ Extract current field mode.
+
+ Returns:
+ `placeholder`/`edit`/`display`
+ """
+ self.wait_for_field(field_id)
+
+ query = self.q(css='.u-field-{}'.format(field_id))
+
+ if not query.present:
+ return None
+
+ field_classes = query.attrs('class')[0].split()
+
+ if 'mode-placeholder' in field_classes:
+ return 'placeholder'
+
+ if 'mode-display' in field_classes:
+ return 'display'
+
+ if 'mode-edit' in field_classes:
+ return 'edit'
+
+ def icon_for_field(self, field_id, icon_id):
+ """
+ Check if field icon is present.
+ """
+ self.wait_for_field(field_id)
+
+ query = self.q(css='.u-field-{} .u-field-icon'.format(field_id))
+ return query.present and icon_id in query.attrs('class')[0].split()
+
def title_for_field(self, field_id):
"""
Return the title of a field.
@@ -79,6 +113,23 @@ class FieldsMixin(object):
"Indicator \"{0}\" is visible.".format(self.indicator_for_field(field_id))
).fulfill()
+ def make_field_editable(self, field_id):
+ """
+ Make a field editable.
+ """
+ query = self.q(css='.u-field-{}'.format(field_id))
+
+ if not query.present:
+ return None
+
+ field_classes = query.attrs('class')[0].split()
+
+ if 'mode-placeholder' in field_classes or 'mode-display' in field_classes:
+ if field_id == 'bio':
+ self.q(css='.u-field-bio > .wrapper-u-field').first.click()
+ else:
+ self.q(css='.u-field-{}'.format(field_id)).first.click()
+
def value_for_readonly_field(self, field_id):
"""
Return the value in a readonly field.
@@ -104,19 +155,54 @@ class FieldsMixin(object):
query.results[0].send_keys(u'\ue007') # Press Enter
return query.attrs('value')[0]
+ def value_for_textarea_field(self, field_id, value=None):
+ """
+ Get or set the value of a textarea field.
+ """
+ self.wait_for_field(field_id)
+
+ self.make_field_editable(field_id)
+
+ query = self.q(css='.u-field-{} textarea'.format(field_id))
+ if not query.present:
+ return None
+
+ if value is not None:
+ query.fill(value)
+ query.results[0].send_keys(u'\ue004') # Focus Out using TAB
+
+ if self.mode_for_field(field_id) == 'edit':
+ return query.text[0]
+ else:
+ return self.get_non_editable_mode_value(field_id)
+
+ def get_non_editable_mode_value(self, field_id):
+ """
+ Return value of field in `display` or `placeholder` mode.
+ """
+ self.wait_for_field(field_id)
+
+ return self.q(css='.u-field-{} .u-field-value'.format(field_id)).text[0]
+
def value_for_dropdown_field(self, field_id, value=None):
"""
Get or set the value in a dropdown field.
"""
self.wait_for_field(field_id)
+ self.make_field_editable(field_id)
+
query = self.q(css='.u-field-{} select'.format(field_id))
if not query.present:
return None
if value is not None:
select_option_by_text(query, value)
- return get_selected_option_text(query)
+
+ if self.mode_for_field(field_id) == 'edit':
+ return get_selected_option_text(query)
+ else:
+ return self.get_non_editable_mode_value(field_id)
def link_title_for_link_field(self, field_id):
"""
diff --git a/common/test/acceptance/pages/lms/learner_profile.py b/common/test/acceptance/pages/lms/learner_profile.py
new file mode 100644
index 0000000000..a27c7b8144
--- /dev/null
+++ b/common/test/acceptance/pages/lms/learner_profile.py
@@ -0,0 +1,151 @@
+"""
+Bok-Choy PageObject class for learner profile page.
+"""
+from . import BASE_URL
+from bok_choy.page_object import PageObject
+from .fields import FieldsMixin
+from bok_choy.promise import EmptyPromise
+
+
+PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
+FIELD_ICONS = {
+ 'country': 'fa-map-marker',
+ 'language_proficiencies': 'fa-comment',
+}
+
+
+class LearnerProfilePage(FieldsMixin, PageObject):
+ """
+ PageObject methods for Learning Profile Page.
+ """
+
+ def __init__(self, browser, username):
+ """
+ Initialize the page.
+
+ Arguments:
+ browser (Browser): The browser instance.
+ username (str): Profile username.
+ """
+ super(LearnerProfilePage, self).__init__(browser)
+ self.username = username
+
+ @property
+ def url(self):
+ """
+ Construct a URL to the page.
+ """
+ return BASE_URL + "/u/" + self.username
+
+ def is_browser_on_page(self):
+ """
+ Check if browser is showing correct page.
+ """
+ return 'Learner Profile' in self.browser.title
+
+ @property
+ def privacy(self):
+ """
+ Get user profile privacy.
+
+ Returns:
+ 'all_users' or 'private'
+ """
+ return 'all_users' if self.q(css=PROFILE_VISIBILITY_SELECTOR.format('all_users')).selected else 'private'
+
+ @privacy.setter
+ def privacy(self, privacy):
+ """
+ Set user profile privacy.
+
+ Arguments:
+ privacy (str): 'all_users' or 'private'
+ """
+ self.wait_for_element_visibility('select#u-field-select-account_privacy', 'Privacy dropdown is visiblie')
+
+ if privacy != self.privacy:
+ self.q(css=PROFILE_VISIBILITY_SELECTOR.format(privacy)).first.click()
+ EmptyPromise(lambda: privacy == self.privacy, 'Privacy is set to {}'.format(privacy)).fulfill()
+ self.wait_for_ajax()
+
+ if privacy == 'all_users':
+ self.wait_for_public_fields()
+
+ def field_is_visible(self, field_id):
+ """
+ Check if a field with id set to `field_id` is shown.
+
+ Arguments:
+ field_id (str): field id
+
+ Returns:
+ True/False
+ """
+ self.wait_for_ajax()
+ return self.q(css='.u-field-{}'.format(field_id)).visible
+
+ def field_is_editable(self, field_id):
+ """
+ Check if a field with id set to `field_id` is editable.
+
+ Arguments:
+ field_id (str): field id
+
+ Returns:
+ True/False
+ """
+ self.wait_for_field(field_id)
+ self.make_field_editable(field_id)
+ return self.mode_for_field(field_id) == 'edit'
+
+ @property
+ def visible_fields(self):
+ """
+ Return list of visible fields.
+ """
+ self.wait_for_field('username')
+
+ fields = ['username', 'country', 'language_proficiencies', 'bio']
+ return [field for field in fields if self.field_is_visible(field)]
+
+ @property
+ def editable_fields(self):
+ """
+ Return list of editable fields currently shown on page.
+ """
+ self.wait_for_ajax()
+ self.wait_for_element_visibility('.u-field-username', 'username is not visible')
+
+ fields = ['country', 'language_proficiencies', 'bio']
+ return [field for field in fields if self.field_is_editable(field)]
+
+ @property
+ def privacy_field_visible(self):
+ """
+ Check if profile visibility selector is shown or not.
+
+ Returns:
+ True/False
+ """
+ self.wait_for_ajax()
+ return self.q(css='#u-field-select-account_privacy').visible
+
+ def field_icon_present(self, field_id):
+ """
+ Check if an icon is present for a field. Only dropdown fields have icons.
+
+ Arguments:
+ field_id (str): field id
+
+ Returns:
+ True/False
+ """
+ return self.icon_for_field(field_id, FIELD_ICONS[field_id])
+
+ def wait_for_public_fields(self):
+ """
+ Wait for `country`, `language` and `bio` fields to be visible.
+ """
+ EmptyPromise(lambda: self.field_is_visible('country'), 'Country field is visible').fulfill()
+ EmptyPromise(lambda: self.field_is_visible('language_proficiencies'), 'Language field is visible').fulfill()
+ EmptyPromise(lambda: self.field_is_visible('bio'), 'About Me field is visible').fulfill()
diff --git a/common/test/acceptance/tests/lms/test_learner_profile.py b/common/test/acceptance/tests/lms/test_learner_profile.py
new file mode 100644
index 0000000000..6fb13b689f
--- /dev/null
+++ b/common/test/acceptance/tests/lms/test_learner_profile.py
@@ -0,0 +1,307 @@
+# -*- coding: utf-8 -*-
+"""
+End-to-end tests for Student's Profile Page.
+"""
+
+from ...pages.lms.account_settings import AccountSettingsPage
+from ...pages.lms.auto_auth import AutoAuthPage
+from ...pages.lms.learner_profile import LearnerProfilePage
+from ...pages.lms.dashboard import DashboardPage
+
+from bok_choy.web_app_test import WebAppTest
+
+
+class LearnerProfilePageTest(WebAppTest):
+ """
+ Tests that verify Student's Profile Page.
+ """
+
+ USER_1_NAME = 'user1'
+ USER_1_EMAIL = 'user1@edx.org'
+ USER_2_NAME = 'user2'
+ USER_2_EMAIL = 'user2@edx.org'
+
+ MY_USER = 1
+ OTHER_USER = 2
+
+ PRIVACY_PUBLIC = 'all_users'
+ PRIVACY_PRIVATE = 'private'
+
+ PUBLIC_PROFILE_FIELDS = ['username', 'country', 'language_proficiencies', 'bio']
+ PRIVATE_PROFILE_FIELDS = ['username']
+
+ PUBLIC_PROFILE_EDITABLE_FIELDS = ['country', 'language_proficiencies', 'bio']
+
+ def setUp(self):
+ """
+ Initialize pages.
+ """
+ super(LearnerProfilePageTest, self).setUp()
+
+ self.account_settings_page = AccountSettingsPage(self.browser)
+ self.dashboard_page = DashboardPage(self.browser)
+
+ self.my_auto_auth_page = AutoAuthPage(self.browser, username=self.USER_1_NAME, email=self.USER_1_EMAIL).visit()
+ self.my_profile_page = LearnerProfilePage(self.browser, self.USER_1_NAME)
+
+ self.other_auto_auth_page = AutoAuthPage(
+ self.browser,
+ username=self.USER_2_NAME,
+ email=self.USER_2_EMAIL
+ ).visit()
+
+ self.other_profile_page = LearnerProfilePage(self.browser, self.USER_2_NAME)
+
+ def authenticate_as_user(self, user):
+ """
+ Auto authenticate a user.
+ """
+ if user == self.MY_USER:
+ self.my_auto_auth_page.visit()
+ elif user == self.OTHER_USER:
+ self.other_auto_auth_page.visit()
+
+ def set_pubilc_profile_fields_data(self, profile_page):
+ """
+ Fill in the public profile fields of a user.
+ """
+ profile_page.value_for_dropdown_field('language_proficiencies', 'English')
+ profile_page.value_for_dropdown_field('country', 'United Kingdom')
+ profile_page.value_for_textarea_field('bio', 'Nothing Special')
+
+ def visit_my_profile_page(self, user, privacy=None):
+ """
+ Visits a users profile page.
+ """
+ self.authenticate_as_user(user)
+ self.my_profile_page.visit()
+ self.my_profile_page.wait_for_page()
+
+ if user is self.MY_USER and privacy is not None:
+ self.my_profile_page.privacy = privacy
+
+ if privacy == self.PRIVACY_PUBLIC:
+ self.set_pubilc_profile_fields_data(self.my_profile_page)
+
+ def visit_other_profile_page(self, user, privacy=None):
+ """
+ Visits a users profile page.
+ """
+ self.authenticate_as_user(user)
+ self.other_profile_page.visit()
+ self.other_profile_page.wait_for_page()
+
+ if user is self.OTHER_USER and privacy is not None:
+ self.account_settings_page.visit()
+ self.account_settings_page.wait_for_page()
+ self.assertEqual(self.account_settings_page.value_for_dropdown_field('year_of_birth', '1980'), '1980')
+
+ self.other_profile_page.visit()
+ self.other_profile_page.wait_for_page()
+ self.other_profile_page.privacy = privacy
+
+ if privacy == self.PRIVACY_PUBLIC:
+ self.set_pubilc_profile_fields_data(self.other_profile_page)
+
+ def test_dashboard_learner_profile_link(self):
+ """
+ Scenario: Verify that my profile link is present on dashboard page and we can navigate to correct page.
+
+ Given that I am a registered user.
+ When I go to Dashboard page.
+ And I click on username dropdown.
+ Then I see My Profile link in the dropdown menu.
+ When I click on My Profile link.
+ Then I will be navigated to My Profile page.
+ """
+ self.dashboard_page.visit()
+ self.dashboard_page.click_username_dropdown()
+ self.assertTrue('My Profile' in self.dashboard_page.username_dropdown_link_text)
+ self.dashboard_page.click_my_profile_link()
+ self.my_profile_page.wait_for_page()
+
+ def test_fields_on_my_private_profile(self):
+ """
+ Scenario: Verify that desired fields are shown when looking at her own private profile.
+
+ Given that I am a registered user.
+ And I visit My Profile page.
+ And I set the profile visibility to private.
+ And I reload the page.
+ Then I should see the profile visibility selector dropdown.
+ Then I see some of the profile fields are shown.
+ """
+ self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PRIVATE)
+
+ self.assertTrue(self.my_profile_page.privacy_field_visible)
+ self.assertEqual(self.my_profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS)
+
+ def test_fields_on_my_public_profile(self):
+ """
+ Scenario: Verify that desired fields are shown when looking at her own public profile.
+
+ Given that I am a registered user.
+ And I visit My Profile page.
+ And I set the profile visibility to public.
+ And I reload the page.
+ Then I should see the profile visibility selector dropdown.
+ Then I see all the profile fields are shown.
+ And `location`, `language` and `about me` fields are editable.
+ """
+ self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC)
+
+ self.assertTrue(self.my_profile_page.privacy_field_visible)
+ self.assertEqual(self.my_profile_page.visible_fields, self.PUBLIC_PROFILE_FIELDS)
+
+ self.assertEqual(self.my_profile_page.editable_fields, self.PUBLIC_PROFILE_EDITABLE_FIELDS)
+
+ def test_fields_on_others_private_profile(self):
+ """
+ Scenario: Verify that desired fields are shown when looking at her own private profile.
+
+ Given that I am a registered user.
+ And I visit others private profile page.
+ Then I shouldn't see the profile visibility selector dropdown.
+ Then I see some of the profile fields are shown.
+ """
+ self.visit_other_profile_page(self.OTHER_USER, privacy=self.PRIVACY_PRIVATE)
+ self.visit_other_profile_page(self.MY_USER)
+
+ self.assertFalse(self.other_profile_page.privacy_field_visible)
+ self.assertEqual(self.other_profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS)
+
+ def test_fields_on_others_public_profile(self):
+ """
+ Scenario: Verify that desired fields are shown when looking at her own public profile.
+
+ Given that I am a registered user.
+ And I visit others public profile page.
+ Then I shouldn't see the profile visibility selector dropdown.
+ Then all the profile fields are shown.
+ Then I shouldn't see the profile visibility selector dropdown.
+ Also `location`, `language` and `about me` fields are not editable.
+ """
+ self.visit_other_profile_page(self.OTHER_USER, privacy=self.PRIVACY_PUBLIC)
+ self.visit_other_profile_page(self.MY_USER)
+
+ self.other_profile_page.wait_for_public_fields()
+ self.assertFalse(self.other_profile_page.privacy_field_visible)
+
+ fields_to_check = self.PUBLIC_PROFILE_FIELDS
+ self.assertEqual(self.other_profile_page.visible_fields, fields_to_check)
+
+ self.assertEqual(self.my_profile_page.editable_fields, [])
+
+ def _test_dropdown_field(self, field_id, new_value, displayed_value, mode):
+ """
+ Test behaviour of a dropdown field.
+ """
+ self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC)
+
+ self.my_profile_page.value_for_dropdown_field(field_id, new_value)
+ self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
+ self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
+
+ self.browser.refresh()
+ self.my_profile_page.wait_for_page()
+
+ self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
+ self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
+
+ def _test_textarea_field(self, field_id, new_value, displayed_value, mode):
+ """
+ Test behaviour of a textarea field.
+ """
+ self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC)
+
+ self.my_profile_page.value_for_textarea_field(field_id, new_value)
+ self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
+ self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
+
+ self.browser.refresh()
+ self.my_profile_page.wait_for_page()
+
+ self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value)
+ self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode)
+
+ def test_country_field(self):
+ """
+ Test behaviour of `Country` field.
+
+ Given that I am a registered user.
+ And I visit My Profile page.
+ And I set the profile visibility to public and set default values for public fields.
+ Then I set country value to `Pakistan`.
+ Then displayed country should be `Pakistan` and country field mode should be `display`
+ And I reload the page.
+ Then displayed country should be `Pakistan` and country field mode should be `display`
+ And I make `country` field editable
+ Then `country` field mode should be `edit`
+ And `country` field icon should be visible.
+ """
+ self._test_dropdown_field('country', 'Pakistan', 'Pakistan', 'display')
+
+ self.my_profile_page.make_field_editable('country')
+ self.assertTrue(self.my_profile_page.mode_for_field('country'), 'edit')
+
+ self.assertTrue(self.my_profile_page.field_icon_present('country'))
+
+ def test_language_field(self):
+ """
+ Test behaviour of `Language` field.
+
+ Given that I am a registered user.
+ And I visit My Profile page.
+ And I set the profile visibility to public and set default values for public fields.
+ Then I set language value to `Urdu`.
+ Then displayed language should be `Urdu` and language field mode should be `display`
+ And I reload the page.
+ Then displayed language should be `Urdu` and language field mode should be `display`
+ Then I set empty value for language.
+ Then displayed language should be `Add language` and language field mode should be `placeholder`
+ And I reload the page.
+ Then displayed language should be `Add language` and language field mode should be `placeholder`
+ And I make `language` field editable
+ Then `language` field mode should be `edit`
+ And `language` field icon should be visible.
+ """
+ self._test_dropdown_field('language_proficiencies', 'Urdu', 'Urdu', 'display')
+ self._test_dropdown_field('language_proficiencies', '', 'Add language', 'placeholder')
+
+ self.my_profile_page.make_field_editable('language_proficiencies')
+ self.assertTrue(self.my_profile_page.mode_for_field('language_proficiencies'), 'edit')
+
+ self.assertTrue(self.my_profile_page.field_icon_present('language_proficiencies'))
+
+ def test_about_me_field(self):
+ """
+ Test behaviour of `About Me` field.
+
+ Given that I am a registered user.
+ And I visit My Profile page.
+ And I set the profile visibility to public and set default values for public fields.
+ Then I set about me value to `Eat Sleep Code`.
+ Then displayed about me should be `Eat Sleep Code` and about me field mode should be `display`
+ And I reload the page.
+ Then displayed about me should be `Eat Sleep Code` and about me field mode should be `display`
+ Then I set empty value for about me.
+ Then displayed about me should be `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.` and about me
+ field mode should be `placeholder`
+ And I reload the page.
+ Then displayed about me should be `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.` and about me
+ field mode should be `placeholder`
+ And I make `about me` field editable
+ Then `about me` field mode should be `edit`
+ """
+ placeholder_value = (
+ "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."
+ )
+
+ self._test_textarea_field('bio', 'Eat Sleep Code', 'Eat Sleep Code', 'display')
+ self._test_textarea_field('bio', '', placeholder_value, 'placeholder')
+
+ self.my_profile_page.make_field_editable('bio')
+ self.assertTrue(self.my_profile_page.mode_for_field('bio'), 'edit')
diff --git a/lms/djangoapps/student_profile/__init__.py b/lms/djangoapps/student_profile/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lms/djangoapps/student_profile/test/__init__.py b/lms/djangoapps/student_profile/test/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lms/djangoapps/student_profile/test/test_views.py b/lms/djangoapps/student_profile/test/test_views.py
new file mode 100644
index 0000000000..e09e2c0501
--- /dev/null
+++ b/lms/djangoapps/student_profile/test/test_views.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+""" Tests for student profile views. """
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+
+from util.testing import UrlResetMixin
+from student.tests.factories import UserFactory
+
+from student_profile.views import learner_profile_context
+
+
+class LearnerProfileViewTest(UrlResetMixin, TestCase):
+ """ Tests for the student profile view. """
+
+ USERNAME = "username"
+ PASSWORD = "password"
+ 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',
+ ]
+
+ def setUp(self):
+ super(LearnerProfileViewTest, self).setUp()
+ self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
+ self.client.login(username=self.USERNAME, password=self.PASSWORD)
+
+ def test_context(self):
+ """
+ Verify learner profile page context data.
+ """
+ context = learner_profile_context(self.user.username, self.USERNAME, self.user.is_staff)
+
+ self.assertEqual(
+ context['data']['default_public_account_fields'],
+ settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields']
+ )
+
+ self.assertEqual(
+ context['data']['accounts_api_url'],
+ reverse("accounts_api", kwargs={'username': self.user.username})
+ )
+
+ self.assertEqual(
+ context['data']['preferences_api_url'],
+ reverse('preferences_api', kwargs={'username': self.user.username})
+ )
+
+ self.assertEqual(context['data']['account_settings_page_url'], reverse('account_settings'))
+
+ for attribute in self.CONTEXT_DATA:
+ self.assertIn(attribute, 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.assertIn(attribute, response.content)
diff --git a/lms/djangoapps/student_profile/views.py b/lms/djangoapps/student_profile/views.py
new file mode 100644
index 0000000000..cd476583b9
--- /dev/null
+++ b/lms/djangoapps/student_profile/views.py
@@ -0,0 +1,71 @@
+""" Views for a student's profile information. """
+
+from django.conf import settings
+from django_countries import countries
+
+from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
+from django.views.decorators.http import require_http_methods
+
+from edxmako.shortcuts import render_to_response
+
+
+@login_required
+@require_http_methods(['GET'])
+def learner_profile(request, username):
+ """
+ Render the students profile page.
+
+ 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
+
+ Example usage:
+ GET /account/profile
+ """
+ return render_to_response(
+ 'student_profile/learner_profile.html',
+ learner_profile_context(request.user.username, username, request.user.is_staff)
+ )
+
+
+def learner_profile_context(logged_in_username, profile_username, user_is_staff):
+ """
+ Context for the learner profile page.
+
+ Args:
+ logged_in_username (str): Username of user logged In user.
+ profile_username (str): username of user whose profile is requested.
+ user_is_staff (bool): Logged In user has staff access.
+
+ Returns:
+ dict
+ """
+ language_options = [language for language in settings.ALL_LANGUAGES]
+
+ country_options = [
+ (country_code, unicode(country_name))
+ for country_code, country_name in sorted(
+ countries.countries, key=lambda(__, name): unicode(name)
+ )
+ ]
+
+ context = {
+ 'data': {
+ 'default_public_account_fields': settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'],
+ 'accounts_api_url': reverse("accounts_api", kwargs={'username': profile_username}),
+ 'preferences_api_url': reverse('preferences_api', kwargs={'username': profile_username}),
+ 'account_settings_page_url': reverse('account_settings'),
+ 'has_preferences_access': (logged_in_username == profile_username or user_is_staff),
+ 'own_profile': (logged_in_username == profile_username),
+ 'country_options': country_options,
+ 'language_options': language_options,
+ }
+ }
+
+ return context
diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js
index b68ba7a4fa..8bebcf05dd 100644
--- a/lms/static/js/spec/main.js
+++ b/lms/static/js/spec/main.js
@@ -90,6 +90,9 @@
'js/student_account/views/RegisterView': 'js/student_account/views/RegisterView',
'js/student_account/views/AccessView': 'js/student_account/views/AccessView',
'js/student_profile/profile': 'js/student_profile/profile',
+ 'js/student_profile/views/learner_profile_fields': 'js/student_profile/views/learner_profile_fields',
+ 'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory',
+ 'js/student_profile/views/learner_profile_view': 'js/student_profile/views/learner_profile_view',
// edxnotes
'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min'
@@ -593,6 +596,8 @@
'lms/include/js/spec/student_account/account_settings_view_spec.js',
'lms/include/js/spec/student_profile/profile_spec.js',
'lms/include/js/spec/views/fields_spec.js',
+ 'lms/include/js/spec/student_profile/learner_profile_factory_spec.js',
+ 'lms/include/js/spec/student_profile/learner_profile_view_spec.js',
'lms/include/js/spec/verify_student/pay_and_verify_view_spec.js',
'lms/include/js/spec/verify_student/webcam_photo_view_spec.js',
'lms/include/js/spec/verify_student/image_input_spec.js',
diff --git a/lms/static/js/spec/student_account/account_settings_factory_spec.js b/lms/static/js/spec/student_account/account_settings_factory_spec.js
index d927241e53..4f993b1a57 100644
--- a/lms/static/js/spec/student_account/account_settings_factory_spec.js
+++ b/lms/static/js/spec/student_account/account_settings_factory_spec.js
@@ -132,7 +132,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
expect(sectionsData[0].fields.length).toBe(5);
var textFields = [sectionsData[0].fields[1], sectionsData[0].fields[2]];
- for (var i = 0; i < textFields ; i++) {
+ for (var i = 0; i < textFields.length ; i++) {
var view = textFields[i].view;
FieldViewsSpecHelpers.verifyTextField(view, {
@@ -154,9 +154,9 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
title: view.options.title,
valueAttribute: view.options.valueAttribute,
helpMessage: '',
- validValue: Helpers.FIELD_OPTIONS[0][0],
- invalidValue1: Helpers.FIELD_OPTIONS[1][0],
- invalidValue2: Helpers.FIELD_OPTIONS[2][0],
+ validValue: Helpers.FIELD_OPTIONS[1][0],
+ invalidValue1: Helpers.FIELD_OPTIONS[2][0],
+ invalidValue2: Helpers.FIELD_OPTIONS[3][0],
validationError: "Nope, this will not do!"
}, requests);
}
diff --git a/lms/static/js/spec/student_account/helpers.js b/lms/static/js/spec/student_account/helpers.js
index a19bda2c52..f110bcfcce 100644
--- a/lms/static/js/spec/student_account/helpers.js
+++ b/lms/static/js/spec/student_account/helpers.js
@@ -9,11 +9,13 @@ define(['underscore'], function(_) {
name: 'Student',
email: 'student@edx.org',
- level_of_education: '1',
- gender: '2',
- year_of_birth: '3',
- country: '1',
- language: '2'
+ level_of_education: '0',
+ gender: '0',
+ year_of_birth: '0',
+ country: '0',
+ language: '0',
+ bio: "About the student",
+ language_proficiencies: [{code: '1'}]
};
var USER_PREFERENCES_DATA = {
@@ -21,6 +23,7 @@ define(['underscore'], function(_) {
};
var FIELD_OPTIONS = [
+ ['0', 'Option 0'],
['1', 'Option 1'],
['2', 'Option 2'],
['3', 'Option 3'],
@@ -28,9 +31,9 @@ define(['underscore'], function(_) {
var expectLoadingIndicatorIsVisible = function (view, visible) {
if (visible) {
- expect(view.$('.ui-loading-indicator')).not.toHaveClass('is-hidden');
+ expect($('.ui-loading-indicator')).not.toHaveClass('is-hidden');
} else {
- expect(view.$('.ui-loading-indicator')).toHaveClass('is-hidden');
+ expect($('.ui-loading-indicator')).toHaveClass('is-hidden');
}
};
@@ -95,6 +98,6 @@ define(['underscore'], function(_) {
expectLoadingErrorIsVisible: expectLoadingErrorIsVisible,
expectElementContainsField: expectElementContainsField,
expectSettingsSectionsButNotFieldsToBeRendered: expectSettingsSectionsButNotFieldsToBeRendered,
- expectSettingsSectionsAndFieldsToBeRendered: expectSettingsSectionsAndFieldsToBeRendered
+ expectSettingsSectionsAndFieldsToBeRendered: expectSettingsSectionsAndFieldsToBeRendered,
};
});
diff --git a/lms/static/js/spec/student_profile/helpers.js b/lms/static/js/spec/student_profile/helpers.js
new file mode 100644
index 0000000000..777061d69c
--- /dev/null
+++ b/lms/static/js/spec/student_profile/helpers.js
@@ -0,0 +1,100 @@
+define(['underscore'], function(_) {
+ 'use strict';
+
+ var expectProfileElementContainsField = function(element, view) {
+ var $element = $(element);
+ var fieldTitle = $element.find('.u-field-title').text().trim();
+
+ if (!_.isUndefined(view.options.title)) {
+ expect(fieldTitle).toBe(view.options.title);
+ }
+
+ if ('fieldValue' in view) {
+ expect(view.model.get(view.options.valueAttribute)).toBeTruthy();
+
+ if (view.fieldValue()) {
+ expect(view.fieldValue()).toBe(view.modelValue());
+
+ } else if ('optionForValue' in view) {
+ expect($($element.find('.u-field-value')[0]).text()).toBe(view.displayValue(view.modelValue()));
+
+ }else {
+ expect($($element.find('.u-field-value')[0]).text()).toBe(view.modelValue());
+ }
+ } else {
+ throw new Error('Unexpected field type: ' + view.fieldType);
+ }
+ };
+
+ var expectProfilePrivacyFieldTobeRendered = function(learnerProfileView, othersProfile) {
+
+ var accountPrivacyElement = learnerProfileView.$('.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');
+
+ expect(sectionOneFieldElements.length).toBe(learnerProfileView.options.sectionOneFieldViews.length);
+
+ _.each(sectionOneFieldElements, function (sectionFieldElement, fieldIndex) {
+ expectProfileElementContainsField(sectionFieldElement, learnerProfileView.options.sectionOneFieldViews[fieldIndex]);
+ });
+ };
+
+ var expectSectionTwoTobeRendered = function(learnerProfileView) {
+
+ var sectionTwoElement = learnerProfileView.$('.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) {
+ expectProfilePrivacyFieldTobeRendered(learnerProfileView, othersProfile);
+
+ var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
+
+ expect(sectionOneFieldElements.length).toBe(1);
+ _.each(sectionOneFieldElements, function (sectionFieldElement, fieldIndex) {
+ expectProfileElementContainsField(sectionFieldElement, learnerProfileView.options.sectionOneFieldViews[fieldIndex]);
+ });
+
+ if (othersProfile) {
+ expect($('.profile-private--message').text()).toBe('This edX learner is currently sharing a limited profile.')
+ } else {
+ expect($('.profile-private--message').text()).toBe('You are currently sharing a limited profile.')
+ }
+ };
+
+ 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);
+ };
+
+ return {
+ expectLimitedProfileSectionsAndFieldsToBeRendered: expectLimitedProfileSectionsAndFieldsToBeRendered,
+ expectProfileSectionsAndFieldsToBeRendered: expectProfileSectionsAndFieldsToBeRendered,
+ expectProfileSectionsNotToBeRendered: expectProfileSectionsNotToBeRendered
+
+ };
+});
diff --git a/lms/static/js/spec/student_profile/learner_profile_factory_spec.js b/lms/static/js/spec/student_profile/learner_profile_factory_spec.js
new file mode 100644
index 0000000000..79807d5ea3
--- /dev/null
+++ b/lms/static/js/spec/student_profile/learner_profile_factory_spec.js
@@ -0,0 +1,150 @@
+define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'js/common_helpers/template_helpers',
+ 'js/spec/student_account/helpers',
+ 'js/spec/student_profile/helpers',
+ 'js/views/fields',
+ 'js/student_account/models/user_account_model',
+ 'js/student_account/models/user_preferences_model',
+ 'js/student_profile/views/learner_profile_view',
+ 'js/student_profile/views/learner_profile_fields',
+ 'js/student_profile/views/learner_profile_factory'
+ ],
+ function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews, UserAccountModel, UserPreferencesModel,
+ LearnerProfileView, LearnerProfileFields, LearnerProfilePage) {
+ 'use strict';
+
+ describe("edx.user.LearnerProfileFactory", function () {
+
+ var requests;
+
+ beforeEach(function () {
+ setFixtures('