Merge pull request #7123 from edx/christina/account-api
Add name and email change requests to accounts API.
This commit is contained in:
@@ -282,33 +282,6 @@ class UserProfile(models.Model):
|
||||
self.set_meta(meta)
|
||||
self.save()
|
||||
|
||||
@transaction.commit_on_success
|
||||
def update_name(self, new_name):
|
||||
"""Update the user's name, storing the old name in the history.
|
||||
|
||||
Implicitly saves the model.
|
||||
If the new name is not the same as the old name, do nothing.
|
||||
|
||||
Arguments:
|
||||
new_name (unicode): The new full name for the user.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
if self.name == new_name:
|
||||
return
|
||||
|
||||
if self.name:
|
||||
meta = self.get_meta()
|
||||
if 'old_names' not in meta:
|
||||
meta['old_names'] = []
|
||||
meta['old_names'].append([self.name, u"", datetime.now(UTC).isoformat()])
|
||||
self.set_meta(meta)
|
||||
|
||||
self.name = new_name
|
||||
self.save()
|
||||
|
||||
|
||||
class UserSignupSource(models.Model):
|
||||
"""
|
||||
|
||||
@@ -4,7 +4,9 @@ import django.db
|
||||
import unittest
|
||||
|
||||
from student.tests.factories import UserFactory, RegistrationFactory, PendingEmailChangeFactory
|
||||
from student.views import reactivation_email_for_user, change_email_request, confirm_email_change
|
||||
from student.views import (
|
||||
reactivation_email_for_user, change_email_request, do_email_change_request, confirm_email_change
|
||||
)
|
||||
from student.models import UserProfile, PendingEmailChange
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
@@ -174,6 +176,11 @@ class EmailChangeRequestTests(TestCase):
|
||||
self.request.POST['new_email'] = email
|
||||
self.assertFailedRequest(self.run_request(), 'Valid e-mail address required.')
|
||||
|
||||
def test_change_email_to_existing_value(self):
|
||||
""" Test the error message if user attempts to change email to the existing value. """
|
||||
self.request.POST['new_email'] = self.user.email
|
||||
self.assertFailedRequest(self.run_request(), 'Old email is the same as the new email.')
|
||||
|
||||
def check_duplicate_email(self, email):
|
||||
"""Test that a request to change a users email to `email` fails"""
|
||||
request = self.req_factory.post('unused_url', data={
|
||||
@@ -192,7 +199,33 @@ class EmailChangeRequestTests(TestCase):
|
||||
UserFactory.create(email=self.new_email)
|
||||
self.check_duplicate_email(self.new_email.capitalize())
|
||||
|
||||
# TODO: Finish testing the rest of change_email_request
|
||||
@patch('django.core.mail.send_mail')
|
||||
@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
|
||||
def test_email_failure(self, send_mail):
|
||||
""" Test the return value if sending the email for the user to click fails. """
|
||||
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.')
|
||||
|
||||
@patch('django.core.mail.send_mail')
|
||||
@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
|
||||
def test_email_success(self, send_mail):
|
||||
""" Test email was sent if no errors encountered. """
|
||||
old_email = self.user.email
|
||||
new_email = "valid@example.com"
|
||||
registration_key = "test registration key"
|
||||
do_email_change_request(self.user, new_email, registration_key)
|
||||
context = {
|
||||
'key': registration_key,
|
||||
'old_email': old_email,
|
||||
'new_email': new_email
|
||||
}
|
||||
send_mail.assert_called_with(
|
||||
mock_render_to_string('emails/email_change_subject.txt', context),
|
||||
mock_render_to_string('emails/email_change.txt', context),
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
[new_email]
|
||||
)
|
||||
|
||||
|
||||
@patch('django.contrib.auth.models.User.email_user')
|
||||
|
||||
@@ -18,7 +18,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.views import password_reset_confirm
|
||||
from django.contrib import messages
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.mail import send_mail
|
||||
from django.core import mail
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import validate_email, validate_slug, ValidationError
|
||||
from django.db import IntegrityError, transaction
|
||||
@@ -1546,7 +1546,7 @@ def create_account_with_params(request, params):
|
||||
dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
|
||||
message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
|
||||
'-' * 80 + '\n\n' + message)
|
||||
send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
|
||||
mail.send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
|
||||
else:
|
||||
user.email_user(subject, message, from_address)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
@@ -1916,6 +1916,7 @@ def reactivation_email_for_user(user):
|
||||
return JsonResponse({"success": True})
|
||||
|
||||
|
||||
# TODO: delete this method and redirect unit tests to do_email_change_request after accounts page work is done.
|
||||
@ensure_csrf_cookie
|
||||
def change_email_request(request):
|
||||
""" AJAX call from the profile page. User wants a new e-mail.
|
||||
@@ -1933,39 +1934,44 @@ def change_email_request(request):
|
||||
}) # TODO: this should be status code 400 # pylint: disable=fixme
|
||||
|
||||
new_email = request.POST['new_email']
|
||||
try:
|
||||
do_email_change_request(request.user, new_email)
|
||||
except ValueError as err:
|
||||
return JsonResponse({
|
||||
"success": False,
|
||||
"error": err.message,
|
||||
})
|
||||
return JsonResponse({"success": True})
|
||||
|
||||
|
||||
def do_email_change_request(user, new_email, activation_key=uuid.uuid4().hex):
|
||||
"""
|
||||
Given a new email for a user, does some basic verification of the new address and sends an activation message
|
||||
to the new address. If any issues are encountered with verification or sending the message, a ValueError will
|
||||
be thrown.
|
||||
"""
|
||||
try:
|
||||
validate_email(new_email)
|
||||
except ValidationError:
|
||||
return JsonResponse({
|
||||
"success": False,
|
||||
"error": _('Valid e-mail address required.'),
|
||||
}) # TODO: this should be status code 400 # pylint: disable=fixme
|
||||
raise ValueError(_('Valid e-mail address required.'))
|
||||
|
||||
if new_email == user.email:
|
||||
raise ValueError(_('Old email is the same as the new email.'))
|
||||
|
||||
if User.objects.filter(email=new_email).count() != 0:
|
||||
## CRITICAL TODO: Handle case sensitivity for e-mails
|
||||
return JsonResponse({
|
||||
"success": False,
|
||||
"error": _('An account with this e-mail already exists.'),
|
||||
}) # TODO: this should be status code 400 # pylint: disable=fixme
|
||||
raise ValueError(_('An account with this e-mail already exists.'))
|
||||
|
||||
pec_list = PendingEmailChange.objects.filter(user=request.user)
|
||||
pec_list = PendingEmailChange.objects.filter(user=user)
|
||||
if len(pec_list) == 0:
|
||||
pec = PendingEmailChange()
|
||||
pec.user = user
|
||||
else:
|
||||
pec = pec_list[0]
|
||||
|
||||
pec.new_email = request.POST['new_email']
|
||||
pec.activation_key = uuid.uuid4().hex
|
||||
pec.new_email = new_email
|
||||
pec.activation_key = activation_key
|
||||
pec.save()
|
||||
|
||||
if pec.new_email == user.email:
|
||||
pec.delete()
|
||||
return JsonResponse({
|
||||
"success": False,
|
||||
"error": _('Old email is the same as the new email.'),
|
||||
}) # TODO: this should be status code 400 # pylint: disable=fixme
|
||||
|
||||
context = {
|
||||
'key': pec.activation_key,
|
||||
'old_email': user.email,
|
||||
@@ -1982,15 +1988,10 @@ def change_email_request(request):
|
||||
settings.DEFAULT_FROM_EMAIL
|
||||
)
|
||||
try:
|
||||
send_mail(subject, message, from_address, [pec.new_email])
|
||||
mail.send_mail(subject, message, from_address, [pec.new_email])
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log.error(u'Unable to send email activation link to user from "%s"', from_address, exc_info=True)
|
||||
return JsonResponse({
|
||||
"success": False,
|
||||
"error": _('Unable to send email activation link. Please try again later.')
|
||||
})
|
||||
|
||||
return JsonResponse({"success": True})
|
||||
raise ValueError(_('Unable to send email activation link. Please try again later.'))
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -2059,6 +2060,7 @@ def confirm_email_change(request, key): # pylint: disable=unused-argument
|
||||
raise
|
||||
|
||||
|
||||
# TODO: DELETE AFTER NEW ACCOUNT PAGE DONE
|
||||
@ensure_csrf_cookie
|
||||
@require_POST
|
||||
def change_name_request(request):
|
||||
@@ -2087,45 +2089,7 @@ def change_name_request(request):
|
||||
return JsonResponse({"success": True})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def pending_name_changes(request):
|
||||
""" Web page which allows staff to approve or reject name changes. """
|
||||
if not request.user.is_staff:
|
||||
raise Http404
|
||||
|
||||
students = []
|
||||
for change in PendingNameChange.objects.all():
|
||||
profile = UserProfile.objects.get(user=change.user)
|
||||
students.append({
|
||||
"new_name": change.new_name,
|
||||
"rationale": change.rationale,
|
||||
"old_name": profile.name,
|
||||
"email": change.user.email,
|
||||
"uid": change.user.id,
|
||||
"cid": change.id,
|
||||
})
|
||||
|
||||
return render_to_response("name_changes.html", {"students": students})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def reject_name_change(request):
|
||||
""" JSON: Name change process. Course staff clicks 'reject' on a given name change """
|
||||
if not request.user.is_staff:
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
pnc = PendingNameChange.objects.get(id=int(request.POST['id']))
|
||||
except PendingNameChange.DoesNotExist:
|
||||
return JsonResponse({
|
||||
"success": False,
|
||||
"error": _('Invalid ID'),
|
||||
}) # TODO: this should be status code 400 # pylint: disable=fixme
|
||||
|
||||
pnc.delete()
|
||||
return JsonResponse({"success": True})
|
||||
|
||||
|
||||
# TODO: DELETE AFTER NEW ACCOUNT PAGE DONE
|
||||
def accept_name_change_by_id(uid):
|
||||
"""
|
||||
Accepts the pending name change request for the user represented
|
||||
@@ -2156,20 +2120,6 @@ def accept_name_change_by_id(uid):
|
||||
return JsonResponse({"success": True})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def accept_name_change(request):
|
||||
""" JSON: Name change process. Course staff clicks 'accept' on a given name change
|
||||
|
||||
We used this during the prototype but now we simply record name changes instead
|
||||
of manually approving them. Still keeping this around in case we want to go
|
||||
back to this approval method.
|
||||
"""
|
||||
if not request.user.is_staff:
|
||||
raise Http404
|
||||
|
||||
return accept_name_change_by_id(int(request.POST['id']))
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
|
||||
Reference in New Issue
Block a user