Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f8b37bb
Added javascript library backbone-super.
symbolist Mar 13, 2015
668f256
Account settings page.
symbolist Mar 13, 2015
13a8504
Jasmine tests for account settings page.
muhammad-ammar Mar 14, 2015
e30efbf
Bokchoy tests for account settings page.
symbolist Mar 13, 2015
87ac7c0
Learner profile page.
Mar 26, 2015
22ee09b
Link to learner profile page from username in discussions.
Mar 26, 2015
dc02d31
If user is not above age limit, show restricted learner profile.
Mar 30, 2015
bcd5eaa
Enable third party auth from account settings page.
symbolist Mar 25, 2015
a53f8d7
Third party auth tests should check account_settings_context().
symbolist Mar 30, 2015
144deaf
Remove account settings display and editing from dashboard sidebar.
Mar 27, 2015
48d1a8d
Quality fixes.
symbolist Apr 11, 2015
66a2093
Limit the size of the user profile's "bio" field
andy-armstrong Apr 13, 2015
d11e5b2
Add client-side events for viewing pages
andy-armstrong Apr 9, 2015
dfc14ac
Resolving JS lint errors for account settings & learner profile page.
symbolist Apr 11, 2015
52a1424
Moving the country field to the required section of account setting p…
Apr 14, 2015
cc5cab7
Event change_initiated for password and email change requests.
Apr 14, 2015
25c30a6
Adding functionlity to upload/remove profile image.
symbolist Apr 3, 2015
eab934a
Jasmine tests for upload/remove profile image on learner profile page.
muhammad-ammar Apr 13, 2015
d893d2d
Bok_choy tests for upload/remove profile image.
Apr 10, 2015
528df90
Fixing the SaSS for RTL.
Apr 16, 2015
5426ab0
Accessibility changes for Account Settings and Learner Profile pages
muhammad-ammar Mar 30, 2015
3b37766
DropDownFieldView should show empty option if model value is unset.
Apr 16, 2015
ddafa9f
Removed all references to AUTH_ENTRY_DASHBOARD.
symbolist Apr 17, 2015
16b7966
Merge pull request #7733 from edx/usman/tnl-2024-third-party-auth-cle…
symbolist Apr 17, 2015
d87feae
Fixed gettext references.
muhammad-ammar Apr 16, 2015
d9fe935
Merge pull request #7728 from edx/ammar/i18n-fixes
muhammad-ammar Apr 17, 2015
5085f94
Obey learner profile default visibility
andy-armstrong Apr 17, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cms/static/js/spec/views/group_configuration_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ define([

patchAndVerifyRequest(requests, url, notificationSpy);

AjaxHelpers.respondToDelete(requests);
AjaxHelpers.respondWithNoContent(requests);
ViewHelpers.verifyNotificationHidden(notificationSpy);
expect($(SELECTORS.itemView)).not.toExist();
};
Expand Down
2 changes: 1 addition & 1 deletion cms/static/js/spec/views/pages/course_outline_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ define(["jquery", "sinon", "js/common_helpers/ajax_helpers", "js/views/utils/vie
expect($('.wrapper-alert-announcement')).not.toHaveClass('is-hidden');
$('.dismiss-button').click();
AjaxHelpers.expectJsonRequest(requests, 'DELETE', 'dummy_dismiss_url');
AjaxHelpers.respondToDelete(requests);
AjaxHelpers.respondWithNoContent(requests);
expect($('.wrapper-alert-announcement')).toHaveClass('is-hidden');
});
});
Expand Down
2 changes: 1 addition & 1 deletion cms/static/js/spec/views/pages/index_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ define(["jquery", "js/common_helpers/ajax_helpers", "js/spec_helpers/view_helper
var reloadSpy = spyOn(ViewUtils, 'reload');
$('.dismiss-button').click();
AjaxHelpers.expectJsonRequest(requests, 'DELETE', 'dummy_dismiss_url');
AjaxHelpers.respondToDelete(requests);
AjaxHelpers.respondWithNoContent(requests);
expect(reloadSpy).toHaveBeenCalled();
});

Expand Down
4 changes: 2 additions & 2 deletions common/djangoapps/student/migrations/0047_add_bio_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'UserProfile.bio'
db.add_column('auth_userprofile', 'bio',
self.gf('django.db.models.fields.TextField')(null=True, blank=True),
self.gf('django.db.models.fields.CharField')(max_length=3000, null=True, blank=True, db_index=False),
keep_default=False)


Expand Down Expand Up @@ -152,7 +152,7 @@ def backwards(self, orm):
'student.userprofile': {
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'bio': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'bio': ('django.db.models.fields.CharField', [], {'db_index': 'False', 'null': 'True', 'blank': 'True'}),
'city': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}),
'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def backwards(self, orm):
'student.userprofile': {
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'bio': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'bio': ('django.db.models.fields.CharField', [], {'db_index': 'False', 'null': 'True', 'blank': 'True'}),
'city': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}),
'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def backwards(self, orm):
'student.userprofile': {
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'bio': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'bio': ('django.db.models.fields.CharField', [], {'db_index': 'False', 'null': 'True', 'blank': 'True'}),
'city': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}),
'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
Expand Down
2 changes: 1 addition & 1 deletion common/djangoapps/student/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ class Meta: # pylint: disable=missing-docstring
country = CountryField(blank=True, null=True)
goals = models.TextField(blank=True, null=True)
allow_certificate = models.BooleanField(default=1)
bio = models.TextField(blank=True, null=True)
bio = models.CharField(blank=True, null=True, max_length=3000, db_index=False)
profile_image_uploaded_at = models.DateTimeField(null=True)

@property
Expand Down
11 changes: 9 additions & 2 deletions common/djangoapps/student/tests/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from student.tests.factories import UserFactory, RegistrationFactory, PendingEmailChangeFactory
from student.views import (
reactivation_email_for_user, change_email_request, do_email_change_request, confirm_email_change
reactivation_email_for_user, change_email_request, do_email_change_request, confirm_email_change,
SETTING_CHANGE_INITIATED
)
from student.models import UserProfile, PendingEmailChange
from django.core.urlresolvers import reverse
Expand All @@ -19,6 +20,7 @@
from edxmako.shortcuts import render_to_string
from edxmako.tests import mako_middleware_process_request
from util.request import safe_get_host
from util.testing import EventTestMixin


class TestException(Exception):
Expand Down Expand Up @@ -198,10 +200,11 @@ def test_reactivation_email_success(self, email_user):
self.assertTrue(response_data['success'])


class EmailChangeRequestTests(TestCase):
class EmailChangeRequestTests(EventTestMixin, TestCase):
"""Test changing a user's email address"""

def setUp(self):
super(EmailChangeRequestTests, self).setUp('student.views.tracker')
self.user = UserFactory.create()
self.new_email = 'new.email@edx.org'
self.req_factory = RequestFactory()
Expand Down Expand Up @@ -275,6 +278,7 @@ def test_email_failure(self, send_mail):
send_mail.side_effect = [Exception, None]
self.request.POST['new_email'] = "valid@email.com"
self.assertFailedRequest(self.run_request(), 'Unable to send email activation link. Please try again later.')
self.assert_no_events_were_emitted()

@patch('django.core.mail.send_mail')
@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
Expand All @@ -295,6 +299,9 @@ def test_email_success(self, send_mail):
settings.DEFAULT_FROM_EMAIL,
[new_email]
)
self.assert_event_emitted(
SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'email', old=old_email, new=new_email
)


@patch('django.contrib.auth.models.User.email_user')
Expand Down
35 changes: 30 additions & 5 deletions common/djangoapps/student/tests/test_reset_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@
from mock import Mock, patch
import ddt

from student.views import password_reset, password_reset_confirm_wrapper
from student.views import password_reset, password_reset_confirm_wrapper, SETTING_CHANGE_INITIATED
from student.tests.factories import UserFactory
from student.tests.test_email import mock_render_to_string
from util.testing import EventTestMixin

from test_microsite import fake_site_name


@ddt.ddt
class ResetPasswordTests(TestCase):
class ResetPasswordTests(EventTestMixin, TestCase):
""" Tests that clicking reset password sends email, and doesn't activate the user
"""
request_factory = RequestFactory()

def setUp(self):
super(ResetPasswordTests, self).setUp('student.views.tracker')
self.user = UserFactory.create()
self.user.is_active = False
self.user.save()
Expand All @@ -55,6 +57,7 @@ def test_user_bad_password_reset(self):
'success': True,
'value': "('registration/password_reset_done.html', [])",
})
self.assert_no_events_were_emitted()

@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
def test_nonexist_email_password_reset(self):
Expand All @@ -71,6 +74,7 @@ def test_nonexist_email_password_reset(self):
'success': True,
'value': "('registration/password_reset_done.html', [])",
})
self.assert_no_events_were_emitted()

@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
def test_password_reset_ratelimited(self):
Expand All @@ -88,6 +92,7 @@ def test_password_reset_ratelimited(self):
bad_req = self.request_factory.post('/password_reset/', {'email': 'thisdoesnotexist@foo.com'})
bad_resp = password_reset(bad_req)
self.assertEquals(bad_resp.status_code, 403)
self.assert_no_events_were_emitted()

cache.clear()

Expand All @@ -98,6 +103,7 @@ def test_reset_password_email(self, send_email):
"""Tests contents of reset password email, and that user is not active"""

good_req = self.request_factory.post('/password_reset/', {'email': self.user.email})
good_req.user = self.user
good_resp = password_reset(good_req)
self.assertEquals(good_resp.status_code, 200)
obj = json.loads(good_resp.content)
Expand All @@ -113,6 +119,10 @@ def test_reset_password_email(self, send_email):
self.assertEquals(len(to_addrs), 1)
self.assertIn(self.user.email, to_addrs)

self.assert_event_emitted(
SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None,
)

#test that the user is not active
self.user = User.objects.get(pk=self.user.pk)
self.assertFalse(self.user.is_active)
Expand All @@ -130,12 +140,17 @@ def test_reset_password_email_https(self, is_secure, protocol, send_email):
'/password_reset/', {'email': self.user.email}
)
req.is_secure = Mock(return_value=is_secure)
resp = password_reset(req)
req.user = self.user
password_reset(req)
_, msg, _, _ = send_email.call_args[0]
expected_msg = "Please go to the following page and choose a new password:\n\n" + protocol

self.assertIn(expected_msg, msg)

self.assert_event_emitted(
SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None
)

@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS")
@patch('django.core.mail.send_mail')
@ddt.data(('Crazy Awesome Site', 'Crazy Awesome Site'), (None, 'edX'))
Expand All @@ -150,7 +165,8 @@ def test_reset_password_email_domain(self, domain_override, platform_name, send_
'/password_reset/', {'email': self.user.email}
)
req.get_host = Mock(return_value=domain_override)
resp = password_reset(req)
req.user = self.user
password_reset(req)
_, msg, _, _ = send_email.call_args[0]

reset_msg = "you requested a password reset for your user account at {}"
Expand All @@ -164,6 +180,10 @@ def test_reset_password_email_domain(self, domain_override, platform_name, send_
sign_off = "The {} Team".format(platform_name)
self.assertIn(sign_off, msg)

self.assert_event_emitted(
SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None
)

@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS")
@patch("microsite_configuration.microsite.get_value", fake_site_name)
@patch('django.core.mail.send_mail')
Expand All @@ -176,13 +196,18 @@ def test_reset_password_email_microsite(self, send_email):
'/password_reset/', {'email': self.user.email}
)
req.get_host = Mock(return_value=None)
resp = password_reset(req)
req.user = self.user
password_reset(req)
_, msg, _, _ = send_email.call_args[0]

reset_msg = "you requested a password reset for your user account at openedx.localhost"

self.assertIn(reset_msg, msg)

self.assert_event_emitted(
SETTING_CHANGE_INITIATED, user_id=self.user.id, setting=u'password', old=None, new=None
)

@patch('student.views.password_reset_confirm')
def test_reset_password_bad_token(self, reset_confirm):
"""Tests bad token and uidb36 in password reset"""
Expand Down
65 changes: 27 additions & 38 deletions common/djangoapps/student/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@

ReverifyInfo = namedtuple('ReverifyInfo', 'course_id course_name course_number date status display') # pylint: disable=invalid-name

SETTING_CHANGE_INITIATED = 'edx.user.settings.change_initiated'


def csrf_token(context):
"""A csrf token that can be included in a form."""
Expand Down Expand Up @@ -620,40 +622,11 @@ def dashboard(request):

enrolled_courses_either_paid = frozenset(course.id for course, _enrollment in course_enrollment_pairs
if _enrollment.is_paid_course())
# get info w.r.t ExternalAuthMap
external_auth_map = None
try:
external_auth_map = ExternalAuthMap.objects.get(user=user)
except ExternalAuthMap.DoesNotExist:
pass

# If there are *any* denied reverifications that have not been toggled off,
# we'll display the banner
denied_banner = any(item.display for item in reverifications["denied"])

language_options = DarkLangConfig.current().released_languages_list

# add in the default language if it's not in the list of released languages
if settings.LANGUAGE_CODE not in language_options:
language_options.append(settings.LANGUAGE_CODE)
# Re-alphabetize language options
language_options.sort()

# try to get the preferred language for the user
preferred_language_code = preferences_api.get_user_preference(request.user, LANGUAGE_KEY)
# try and get the current language of the user
current_language_code = get_language()
if preferred_language_code and preferred_language_code in settings.LANGUAGE_DICT:
# if the user has a preference, get the name from the code
current_language = settings.LANGUAGE_DICT[preferred_language_code]
elif current_language_code in settings.LANGUAGE_DICT:
# if the user's browser is showing a particular language,
# use that as the current language
current_language = settings.LANGUAGE_DICT[current_language_code]
else:
# otherwise, use the default language
current_language = settings.LANGUAGE_DICT[settings.LANGUAGE_CODE]

# Populate the Order History for the side-bar.
order_history_list = order_history(user, course_org_filter=course_org_filter, org_filter_out_set=org_filter_out_set)

Expand All @@ -678,7 +651,6 @@ def dashboard(request):
'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts,
'message': message,
'external_auth_map': external_auth_map,
'staff_access': staff_access,
'errored_courses': errored_courses,
'show_courseware_links_for': show_courseware_links_for,
Expand All @@ -693,11 +665,7 @@ def dashboard(request):
'block_courses': block_courses,
'denied_banner': denied_banner,
'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
'language_options': language_options,
'current_language': current_language,
'current_language_code': current_language_code,
'user': user,
'duplicate_provider': None,
'logout_url': reverse(logout_user),
'platform_name': platform_name,
'enrolled_courses_either_paid': enrolled_courses_either_paid,
Expand All @@ -707,10 +675,6 @@ def dashboard(request):
'ccx_membership_triplets': ccx_membership_triplets,
}

if third_party_auth.is_enabled():
context['duplicate_provider'] = pipeline.get_duplicate_provider(messages.get_messages(request))
context['provider_user_states'] = pipeline.get_provider_user_states(user)

return render_to_response('dashboard.html', context)


Expand Down Expand Up @@ -1871,6 +1835,18 @@ def password_reset(request):
from_email=settings.DEFAULT_FROM_EMAIL,
request=request,
domain_override=request.get_host())
# When password change is complete, a "edx.user.settings.changed" event will be emitted.
# But because changing the password is multi-step, we also emit an event here so that we can
# track where the request was initiated.
tracker.emit(
SETTING_CHANGE_INITIATED,
{
"setting": "password",
"old": None,
"new": None,
"user_id": request.user.id,
}
)
else:
# bad user? tick the rate limiter counter
AUDIT_LOG.info("Bad password_reset user passed in.")
Expand Down Expand Up @@ -2087,6 +2063,19 @@ def do_email_change_request(user, new_email, activation_key=uuid.uuid4().hex):
log.error(u'Unable to send email activation link to user from "%s"', from_address, exc_info=True)
raise ValueError(_('Unable to send email activation link. Please try again later.'))

# When the email address change is complete, a "edx.user.settings.changed" event will be emitted.
# But because changing the email address is multi-step, we also emit an event here so that we can
# track where the request was initiated.
tracker.emit(
SETTING_CHANGE_INITIATED,
{
"setting": "email",
"old": context['old_email'],
"new": context['new_email'],
"user_id": user.id,
}
)


@ensure_csrf_cookie
@transaction.commit_manually
Expand Down
Loading