Ensure account creation succeeds before creating comments service user
Also improves handling in cases where account creation did succeed, but comments service user creation did not. Depends on commit 88abdd8a0b20bc9816f487b25abdea1953f5661d in https://github.com/edx/cs_comments_service JIRA: FOR-522
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from optparse import make_option
|
||||
from django.core.management.base import BaseCommand
|
||||
from student.models import CourseEnrollment, Registration
|
||||
from student.views import _do_create_account
|
||||
from student.models import CourseEnrollment, Registration, create_comments_service_user
|
||||
from student.views import _do_create_account, AccountValidationError
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from track.management.tracked_command import TrackedCommand
|
||||
@@ -74,17 +74,16 @@ class Command(TrackedCommand):
|
||||
'honor_code': u'true',
|
||||
'terms_of_service': u'true',
|
||||
}
|
||||
create_account = _do_create_account(post_data)
|
||||
if isinstance(create_account, tuple):
|
||||
user = create_account[0]
|
||||
try:
|
||||
user, profile, reg = _do_create_account(post_data)
|
||||
if options['staff']:
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
reg = Registration.objects.get(user=user)
|
||||
reg.activate()
|
||||
reg.save()
|
||||
else:
|
||||
print create_account
|
||||
create_comments_service_user(user)
|
||||
except AccountValidationError as e:
|
||||
print e.message
|
||||
user = User.objects.get(email=options['email'])
|
||||
if options['course']:
|
||||
CourseEnrollment.enroll(user, options['course'], mode=options['mode'])
|
||||
|
||||
@@ -831,18 +831,18 @@ def add_user_to_default_group(user, group):
|
||||
utg.save()
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def update_user_information(sender, instance, created, **kwargs):
|
||||
def create_comments_service_user(user):
|
||||
if not settings.FEATURES['ENABLE_DISCUSSION_SERVICE']:
|
||||
# Don't try--it won't work, and it will fill the logs with lots of errors
|
||||
return
|
||||
try:
|
||||
cc_user = cc.User.from_django_user(instance)
|
||||
cc_user = cc.User.from_django_user(user)
|
||||
cc_user.save()
|
||||
except Exception as e:
|
||||
log = logging.getLogger("edx.discussion")
|
||||
log.error(unicode(e))
|
||||
log.error("update user info to discussion failed for user with id: " + str(instance.id))
|
||||
log.error(
|
||||
"Could not create comments service user with id {}".format(user.id),
|
||||
exc_info=True)
|
||||
|
||||
# Define login and logout handlers here in the models file, instead of the views file,
|
||||
# so that they are more likely to be loaded when a Studio user brings up the Studio admin
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
import ddt
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.db.transaction import rollback
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.test.utils import override_settings
|
||||
import mock
|
||||
|
||||
from user_api.models import UserPreference
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
|
||||
import student
|
||||
|
||||
TEST_CS_URL = 'https://comments.service.test:123/'
|
||||
|
||||
@ddt.ddt
|
||||
class TestCreateAccount(TestCase):
|
||||
@@ -41,3 +45,43 @@ class TestCreateAccount(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
user = User.objects.get(username=self.username)
|
||||
self.assertEqual(UserPreference.get_preference(user, LANGUAGE_KEY), lang)
|
||||
|
||||
|
||||
@mock.patch.dict("student.models.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
@mock.patch("lms.lib.comment_client.User.base_url", TEST_CS_URL)
|
||||
@mock.patch("lms.lib.comment_client.utils.requests.request", return_value=mock.Mock(status_code=200, text='{}'))
|
||||
class TestCreateCommentsServiceUser(TransactionTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.username = "test_user"
|
||||
self.url = reverse("create_account")
|
||||
self.params = {
|
||||
"username": self.username,
|
||||
"email": "test@example.org",
|
||||
"password": "testpass",
|
||||
"name": "Test User",
|
||||
"honor_code": "true",
|
||||
"terms_of_service": "true",
|
||||
}
|
||||
|
||||
def test_cs_user_created(self, request):
|
||||
"If user account creation succeeds, we should create a comments service user"
|
||||
response = self.client.post(self.url, self.params)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(request.called)
|
||||
args, kwargs = request.call_args
|
||||
self.assertEqual(args[0], 'put')
|
||||
self.assertTrue(args[1].startswith(TEST_CS_URL))
|
||||
self.assertEqual(kwargs['data']['username'], self.params['username'])
|
||||
|
||||
@mock.patch("student.models.Registration.register", side_effect=Exception)
|
||||
def test_cs_user_not_created(self, register, request):
|
||||
"If user account creation fails, we should not create a comments service user"
|
||||
try:
|
||||
response = self.client.post(self.url, self.params)
|
||||
except:
|
||||
pass
|
||||
with self.assertRaises(User.DoesNotExist):
|
||||
User.objects.get(username=self.username)
|
||||
self.assertTrue(register.called)
|
||||
self.assertFalse(request.called)
|
||||
|
||||
@@ -38,7 +38,8 @@ from course_modes.models import CourseMode
|
||||
from student.models import (
|
||||
Registration, UserProfile, PendingNameChange,
|
||||
PendingEmailChange, CourseEnrollment, unique_id_for_user,
|
||||
CourseEnrollmentAllowed, UserStanding, LoginFailures
|
||||
CourseEnrollmentAllowed, UserStanding, LoginFailures,
|
||||
create_comments_service_user
|
||||
)
|
||||
from student.forms import PasswordResetFormNoActive
|
||||
from student.firebase_token_generator import create_token
|
||||
@@ -951,6 +952,11 @@ def change_setting(request):
|
||||
})
|
||||
|
||||
|
||||
class AccountValidationError(Exception):
|
||||
def __init__(self, message, field):
|
||||
super(AccountValidationError, self).__init__(message)
|
||||
self.field = field
|
||||
|
||||
def _do_create_account(post_vars):
|
||||
"""
|
||||
Given cleaned post variables, create the User and UserProfile objects, as well as the
|
||||
@@ -970,19 +976,19 @@ def _do_create_account(post_vars):
|
||||
try:
|
||||
user.save()
|
||||
except IntegrityError:
|
||||
js = {'success': False}
|
||||
# Figure out the cause of the integrity error
|
||||
if len(User.objects.filter(username=post_vars['username'])) > 0:
|
||||
js['value'] = _("An account with the Public Username '{username}' already exists.").format(username=post_vars['username'])
|
||||
js['field'] = 'username'
|
||||
return JsonResponse(js, status=400)
|
||||
|
||||
if len(User.objects.filter(email=post_vars['email'])) > 0:
|
||||
js['value'] = _("An account with the Email '{email}' already exists.").format(email=post_vars['email'])
|
||||
js['field'] = 'email'
|
||||
return JsonResponse(js, status=400)
|
||||
|
||||
raise
|
||||
raise AccountValidationError(
|
||||
_("An account with the Public Username '{username}' already exists.").format(username=post_vars['username']),
|
||||
field="username"
|
||||
)
|
||||
elif len(User.objects.filter(email=post_vars['email'])) > 0:
|
||||
raise AccountValidationError(
|
||||
_("An account with the Email '{email}' already exists.").format(email=post_vars['email']),
|
||||
field="email"
|
||||
)
|
||||
else:
|
||||
raise
|
||||
|
||||
registration.register(user)
|
||||
|
||||
@@ -1005,6 +1011,7 @@ def _do_create_account(post_vars):
|
||||
profile.save()
|
||||
except Exception:
|
||||
log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
|
||||
raise
|
||||
|
||||
UserPreference.set_preference(user, LANGUAGE_KEY, get_language())
|
||||
|
||||
@@ -1150,11 +1157,17 @@ def create_account(request, post_override=None):
|
||||
return JsonResponse(js, status=400)
|
||||
|
||||
# Ok, looks like everything is legit. Create the account.
|
||||
ret = _do_create_account(post_vars)
|
||||
if isinstance(ret, HttpResponse): # if there was an error then return that
|
||||
return ret
|
||||
try:
|
||||
with transaction.commit_on_success():
|
||||
ret = _do_create_account(post_vars)
|
||||
except AccountValidationError as e:
|
||||
return JsonResponse({'success': False, 'value': e.message, 'field': e.field}, status=400)
|
||||
|
||||
(user, profile, registration) = ret
|
||||
|
||||
dog_stats_api.increment("common.student.account_created")
|
||||
create_comments_service_user(user)
|
||||
|
||||
context = {
|
||||
'name': post_vars['name'],
|
||||
'key': registration.activation_key,
|
||||
@@ -1213,8 +1226,6 @@ def create_account(request, post_override=None):
|
||||
login_user.save()
|
||||
AUDIT_LOG.info(u"Login activated on extauth account - {0} ({1})".format(login_user.username, login_user.email))
|
||||
|
||||
dog_stats_api.increment("common.student.account_created")
|
||||
|
||||
response = JsonResponse({
|
||||
'success': True,
|
||||
'redirect_url': try_change_enrollment(request),
|
||||
@@ -1283,20 +1294,16 @@ def auto_auth(request):
|
||||
|
||||
# Attempt to create the account.
|
||||
# If successful, this will return a tuple containing
|
||||
# the new user object; otherwise it will return an error
|
||||
# message.
|
||||
result = _do_create_account(post_data)
|
||||
|
||||
if isinstance(result, tuple):
|
||||
user = result[0]
|
||||
|
||||
# If we did not create a new account, the user might already
|
||||
# exist. Attempt to retrieve it.
|
||||
else:
|
||||
# the new user object.
|
||||
try:
|
||||
user, profile, reg = _do_create_account(post_data)
|
||||
except AccountValidationError:
|
||||
# Attempt to retrieve the existing user.
|
||||
user = User.objects.get(username=username)
|
||||
user.email = email
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
reg = Registration.objects.get(user=user)
|
||||
|
||||
# Set the user's global staff bit
|
||||
if is_staff is not None:
|
||||
@@ -1304,7 +1311,6 @@ def auto_auth(request):
|
||||
user.save()
|
||||
|
||||
# Activate the user
|
||||
reg = Registration.objects.get(user=user)
|
||||
reg.activate()
|
||||
reg.save()
|
||||
|
||||
@@ -1321,6 +1327,8 @@ def auto_auth(request):
|
||||
user = authenticate(username=username, password=password)
|
||||
login(request, user)
|
||||
|
||||
create_comments_service_user(user)
|
||||
|
||||
# Provide the user with a valid CSRF token
|
||||
# then return a 200 response
|
||||
success_msg = u"Logged in user {0} ({1}) with password {2} and user_id {3}".format(
|
||||
|
||||
@@ -80,7 +80,16 @@ class User(models.Model):
|
||||
retrieve_params = self.default_retrieve_params
|
||||
if self.attributes.get('course_id'):
|
||||
retrieve_params['course_id'] = self.course_id
|
||||
response = perform_request('get', url, retrieve_params)
|
||||
try:
|
||||
response = perform_request('get', url, retrieve_params)
|
||||
except CommentClientRequestError as e:
|
||||
if e.status_code == 404:
|
||||
# attempt to gracefully recover from a previous failure
|
||||
# to sync this user to the comments service.
|
||||
self.save()
|
||||
response = perform_request('get', url, retrieve_params)
|
||||
else:
|
||||
raise
|
||||
self.update_attributes(**response)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user