Пример #1
0
 def do_email_change(self, user, email, activation_key=None):
     """
     Executes do_email_change_request, returning any resulting error message.
     """
     try:
         do_email_change_request(user, email, activation_key)
     except ValueError as err:
         return text_type(err)
Пример #2
0
 def do_email_change(self, user, email, activation_key=None):
     """
     Executes do_email_change_request, returning any resulting error message.
     """
     try:
         do_email_change_request(user, email, activation_key)
     except ValueError as err:
         return text_type(err)
Пример #3
0
 def do_secondary_email_change(self, user, email, activation_key=None):
     """
     Executes do_secondary_email_change_request, returning any resulting error message.
     """
     with patch('crum.get_current_request', return_value=self.fake_request):
         do_email_change_request(user=user,
                                 new_email=email,
                                 activation_key=activation_key,
                                 secondary_email_change_request=True)
Пример #4
0
 def do_secondary_email_change(self, user, email, activation_key=None):
     """
     Executes do_secondary_email_change_request, returning any resulting error message.
     """
     with patch('crum.get_current_request', return_value=self.fake_request):
         do_email_change_request(
             user=user,
             new_email=email,
             activation_key=activation_key,
             secondary_email_change_request=True
         )
Пример #5
0
 def test_email_success(self, send_mail):
     """ Test email was sent if no errors encountered. """
     old_email = self.user.email
     new_email = "*****@*****.**"
     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])
Пример #6
0
 def test_email_success(self, send_mail):
     """ Test email was sent if no errors encountered. """
     old_email = self.user.email
     new_email = "*****@*****.**"
     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]
     )
Пример #7
0
def _send_email_change_requests_if_needed(data, user):
    new_email = data.get("email")
    if new_email:
        try:
            student_views.do_email_change_request(user, new_email)
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(
                    text_type(err)),
                user_message=text_type(err))

    new_secondary_email = data.get("secondary_email")
    if new_secondary_email:
        try:
            student_views.do_email_change_request(
                user=user,
                new_email=new_secondary_email,
                secondary_email_change_request=True,
            )
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(
                    text_type(err)),
                user_message=text_type(err))
Пример #8
0
def _send_email_change_requests_if_needed(data, user):
    new_email = data.get("email")
    if new_email:
        try:
            student_views.do_email_change_request(user, new_email)
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(text_type(err)),
                user_message=text_type(err)
            )

    new_secondary_email = data.get("secondary_email")
    if new_secondary_email:
        try:
            student_views.do_email_change_request(
                user=user,
                new_email=new_secondary_email,
                secondary_email_change_request=True,
            )
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(text_type(err)),
                user_message=text_type(err)
            )
Пример #9
0
def update_account_settings(requesting_user, update, username=None):
    """Update user account information.

    Note:
        It is up to the caller of this method to enforce the contract that this method is only called
        with the user who made the request.

    Arguments:
        requesting_user (User): The user requesting to modify account information. Only the user with username
            'username' has permissions to modify account information.
        update (dict): The updated account field values.
        username (str): Optional username specifying which account should be updated. If not specified,
            `requesting_user.username` is assumed.

    Raises:
        UserNotFound: no user with username `username` exists (or `requesting_user.username` if
            `username` is not specified)
        UserNotAuthorized: the requesting_user does not have access to change the account
            associated with `username`
        AccountValidationError: the update was not attempted because validation errors were found with
            the supplied update
        AccountUpdateError: the update could not be completed. Note that if multiple fields are updated at the same
            time, some parts of the update may have been successful, even if an AccountUpdateError is returned;
            in particular, the user account (not including e-mail address) may have successfully been updated,
            but then the e-mail change request, which is processed last, may throw an error.
        UserAPIInternalError: the operation failed due to an unexpected error.
    """
    if username is None:
        username = requesting_user.username

    existing_user, existing_user_profile = _get_user_and_profile(username)

    if requesting_user.username != username:
        raise UserNotAuthorized()

    # If user has requested to change email, we must call the multi-step process to handle this.
    # It is not handled by the serializer (which considers email to be read-only).
    changing_email = False
    if "email" in update:
        changing_email = True
        new_email = update["email"]
        del update["email"]

    # If user has requested to change name, store old name because we must update associated metadata
    # after the save process is complete.
    old_name = None
    if "name" in update:
        old_name = existing_user_profile.name

    # Check for fields that are not editable. Marking them read-only causes them to be ignored, but we wish to 400.
    read_only_fields = set(update.keys()).intersection(
        AccountUserSerializer.get_read_only_fields() +
        AccountLegacyProfileSerializer.get_read_only_fields())

    # Build up all field errors, whether read-only, validation, or email errors.
    field_errors = {}

    if read_only_fields:
        for read_only_field in read_only_fields:
            field_errors[read_only_field] = {
                "developer_message":
                u"This field is not editable via this API",
                "user_message":
                _(u"The '{field_name}' field cannot be edited.").format(
                    field_name=read_only_field)
            }
            del update[read_only_field]

    user_serializer = AccountUserSerializer(existing_user, data=update)
    legacy_profile_serializer = AccountLegacyProfileSerializer(
        existing_user_profile, data=update)

    for serializer in user_serializer, legacy_profile_serializer:
        field_errors = add_serializer_errors(serializer, update, field_errors)

    # If the user asked to change email, validate it.
    if changing_email:
        try:
            student_views.validate_new_email(existing_user, new_email)
        except ValueError as err:
            field_errors["email"] = {
                "developer_message":
                u"Error thrown from validate_new_email: '{}'".format(
                    err.message),
                "user_message":
                err.message
            }

    # If we have encountered any validation errors, return them to the user.
    if field_errors:
        raise AccountValidationError(field_errors)

    try:
        # If everything validated, go ahead and save the serializers.

        # We have not found a way using signals to get the language proficiency changes (grouped by user).
        # As a workaround, store old and new values here and emit them after save is complete.
        if "language_proficiencies" in update:
            old_language_proficiencies = legacy_profile_serializer.data[
                "language_proficiencies"]

        for serializer in user_serializer, legacy_profile_serializer:
            serializer.save()

        # if any exception is raised for user preference (i.e. account_privacy), the entire transaction for user account
        # patch is rolled back and the data is not saved
        if 'account_privacy' in update:
            update_user_preferences(
                requesting_user,
                {'account_privacy': update["account_privacy"]}, existing_user)

        if "language_proficiencies" in update:
            new_language_proficiencies = update["language_proficiencies"]
            emit_setting_changed_event(
                user=existing_user,
                db_table=existing_user_profile.language_proficiencies.model.
                _meta.db_table,
                setting_name="language_proficiencies",
                old_value=old_language_proficiencies,
                new_value=new_language_proficiencies,
            )

        # If the name was changed, store information about the change operation. This is outside of the
        # serializer so that we can store who requested the change.
        if old_name:
            meta = existing_user_profile.get_meta()
            if 'old_names' not in meta:
                meta['old_names'] = []
            meta['old_names'].append([
                old_name,
                u"Name change requested through account API by {0}".format(
                    requesting_user.username),
                datetime.datetime.now(UTC).isoformat()
            ])
            existing_user_profile.set_meta(meta)
            existing_user_profile.save()

    except PreferenceValidationError as err:
        raise AccountValidationError(err.preference_errors)
    except Exception as err:
        raise AccountUpdateError(
            u"Error thrown when saving account updates: '{}'".format(
                err.message))

    # And try to send the email change request if necessary.
    if changing_email:
        try:
            student_views.do_email_change_request(existing_user, new_email)
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(
                    err.message),
                user_message=err.message)
Пример #10
0
def update_account_settings(requesting_user, update, username=None):
    """Update user account information.

    Note:
        It is up to the caller of this method to enforce the contract that this method is only called
        with the user who made the request.

    Arguments:
        requesting_user (User): The user requesting to modify account information. Only the user with username
            'username' has permissions to modify account information.
        update (dict): The updated account field values.
        username (str): Optional username specifying which account should be updated. If not specified,
            `requesting_user.username` is assumed.

    Raises:
        UserNotFound: no user with username `username` exists (or `requesting_user.username` if
            `username` is not specified)
        UserNotAuthorized: the requesting_user does not have access to change the account
            associated with `username`
        AccountValidationError: the update was not attempted because validation errors were found with
            the supplied update
        AccountUpdateError: the update could not be completed. Note that if multiple fields are updated at the same
            time, some parts of the update may have been successful, even if an AccountUpdateError is returned;
            in particular, the user account (not including e-mail address) may have successfully been updated,
            but then the e-mail change request, which is processed last, may throw an error.
        UserAPIInternalError: the operation failed due to an unexpected error.
    """
    if username is None:
        username = requesting_user.username

    existing_user, existing_user_profile = _get_user_and_profile(username)

    if requesting_user.username != username:
        raise UserNotAuthorized()

    # If user has requested to change email, we must call the multi-step process to handle this.
    # It is not handled by the serializer (which considers email to be read-only).
    changing_email = False
    if "email" in update:
        changing_email = True
        new_email = update["email"]
        del update["email"]

    # If user has requested to change name, store old name because we must update associated metadata
    # after the save process is complete.
    old_name = None
    if "name" in update:
        old_name = existing_user_profile.name

    # Check for fields that are not editable. Marking them read-only causes them to be ignored, but we wish to 400.
    read_only_fields = set(update.keys()).intersection(
        AccountUserSerializer.get_read_only_fields() + AccountLegacyProfileSerializer.get_read_only_fields()
    )

    # Build up all field errors, whether read-only, validation, or email errors.
    field_errors = {}

    if read_only_fields:
        for read_only_field in read_only_fields:
            field_errors[read_only_field] = {
                "developer_message": u"This field is not editable via this API",
                "user_message": _(u"The '{field_name}' field cannot be edited.").format(field_name=read_only_field)
            }
            del update[read_only_field]

    user_serializer = AccountUserSerializer(existing_user, data=update)
    legacy_profile_serializer = AccountLegacyProfileSerializer(existing_user_profile, data=update)

    for serializer in user_serializer, legacy_profile_serializer:
        field_errors = add_serializer_errors(serializer, update, field_errors)

    # If the user asked to change email, validate it.
    if changing_email:
        try:
            student_views.validate_new_email(existing_user, new_email)
        except ValueError as err:
            field_errors["email"] = {
                "developer_message": u"Error thrown from validate_new_email: '{}'".format(err.message),
                "user_message": err.message
            }

    # If we have encountered any validation errors, return them to the user.
    if field_errors:
        raise AccountValidationError(field_errors)

    try:
        # If everything validated, go ahead and save the serializers.

        # We have not found a way using signals to get the language proficiency changes (grouped by user).
        # As a workaround, store old and new values here and emit them after save is complete.
        if "language_proficiencies" in update:
            old_language_proficiencies = legacy_profile_serializer.data["language_proficiencies"]

        for serializer in user_serializer, legacy_profile_serializer:
            serializer.save()

        if "language_proficiencies" in update:
            new_language_proficiencies = update["language_proficiencies"]
            emit_setting_changed_event(
                user=existing_user,
                db_table=existing_user_profile.language_proficiencies.model._meta.db_table,
                setting_name="language_proficiencies",
                old_value=old_language_proficiencies,
                new_value=new_language_proficiencies,
            )

        # If the name was changed, store information about the change operation. This is outside of the
        # serializer so that we can store who requested the change.
        if old_name:
            meta = existing_user_profile.get_meta()
            if 'old_names' not in meta:
                meta['old_names'] = []
            meta['old_names'].append([
                old_name,
                u"Name change requested through account API by {0}".format(requesting_user.username),
                datetime.datetime.now(UTC).isoformat()
            ])
            existing_user_profile.set_meta(meta)
            existing_user_profile.save()

    except Exception as err:
        raise AccountUpdateError(
            u"Error thrown when saving account updates: '{}'".format(err.message)
        )

    # And try to send the email change request if necessary.
    if changing_email:
        try:
            student_views.do_email_change_request(existing_user, new_email)
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(err.message),
                user_message=err.message
            )
Пример #11
0
    def update_account(requesting_user, username, update):
        """Update the account for the given username.

        Note:
            No authorization or permissions checks are done in this method. It is up to the caller
            of this method to enforce the contract that this method is only called if
            requesting_user.username == username or requesting_user.is_staff == True.

        Arguments:
            requesting_user (User): the user who initiated the request
            username (string): the username associated with the account to change
            update (dict): the updated account field values

        Raises:
            AccountUserNotFound: no user exists with the specified username
            AccountUpdateError: the update could not be completed, usually due to validation errors
                (for example, read-only fields were specified or field values are not legal)
        """
        existing_user, existing_user_profile = AccountView._get_user_and_profile(username)

        # If user has requested to change email, we must call the multi-step process to handle this.
        # It is not handled by the serializer (which considers email to be read-only).
        new_email = None
        if "email" in update:
            new_email = update["email"]
            del update["email"]

        # If user has requested to change name, store old name because we must update associated metadata
        # after the save process is complete.
        old_name = None
        if "name" in update:
            old_name = existing_user_profile.name

        # Check for fields that are not editable. Marking them read-only causes them to be ignored, but we wish to 400.
        read_only_fields = set(update.keys()).intersection(
            AccountUserSerializer.Meta.read_only_fields + AccountLegacyProfileSerializer.Meta.read_only_fields
        )
        if read_only_fields:
            field_errors = {}
            for read_only_field in read_only_fields:
                field_errors[read_only_field] = {
                    "developer_message": "This field is not editable via this API",
                    "user_message": _("Field '{field_name}' cannot be edited.".format(field_name=read_only_field))
                }
            raise AccountUpdateError({"field_errors": field_errors})

        # If the user asked to change email, send the request now.
        if new_email:
            try:
                do_email_change_request(existing_user, new_email)
            except ValueError as err:
                response_data = {
                    "developer_message": "Error thrown from do_email_change_request: '{}'".format(err.message),
                    "user_message": err.message
                }
                raise AccountUpdateError(response_data)

        user_serializer = AccountUserSerializer(existing_user, data=update)
        legacy_profile_serializer = AccountLegacyProfileSerializer(existing_user_profile, data=update)

        for serializer in user_serializer, legacy_profile_serializer:
            validation_errors = AccountView._get_validation_errors(update, serializer)
            if validation_errors:
                raise AccountUpdateError(validation_errors)
            serializer.save()

        # If the name was changed, store information about the change operation. This is outside of the
        # serializer so that we can store who requested the change.
        if old_name:
            meta = existing_user_profile.get_meta()
            if 'old_names' not in meta:
                meta['old_names'] = []
            meta['old_names'].append([
                old_name,
                "Name change requested through account API by {0}".format(requesting_user.username),
                datetime.datetime.now(UTC).isoformat()
            ])
            existing_user_profile.set_meta(meta)
            existing_user_profile.save()
Пример #12
0
def update_account_settings(requesting_user, update, username=None):
    """Update user account information.

    Note:
        It is up to the caller of this method to enforce the contract that this method is only called
        with the user who made the request.

    Arguments:
        requesting_user (User): The user requesting to modify account information. Only the user with username
            'username' has permissions to modify account information.
        update (dict): The updated account field values.
        username (str): Optional username specifying which account should be updated. If not specified,
            `requesting_user.username` is assumed.

    Raises:
        errors.UserNotFound: no user with username `username` exists (or `requesting_user.username` if
            `username` is not specified)
        errors.UserNotAuthorized: the requesting_user does not have access to change the account
            associated with `username`
        errors.AccountValidationError: the update was not attempted because validation errors were found with
            the supplied update
        errors.AccountUpdateError: the update could not be completed. Note that if multiple fields are updated at the
            same time, some parts of the update may have been successful, even if an errors.AccountUpdateError is
            returned; in particular, the user account (not including e-mail address) may have successfully been updated,
            but then the e-mail change request, which is processed last, may throw an error.
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    if username is None:
        username = requesting_user.username

    existing_user, existing_user_profile = _get_user_and_profile(username)
    account_recovery = _get_account_recovery(existing_user)

    if requesting_user.username != username:
        raise errors.UserNotAuthorized()

    # If user has requested to change email, we must call the multi-step process to handle this.
    # It is not handled by the serializer (which considers email to be read-only).
    changing_email = False
    if "email" in update:
        changing_email = True
        new_email = update["email"]
        del update["email"]

    # If user has requested to change name, store old name because we must update associated metadata
    # after the save process is complete.
    changing_full_name = False
    old_name = None
    if "name" in update:
        changing_full_name = True
        old_name = existing_user_profile.name

    changing_secondary_email = False
    if "secondary_email" in update:
        changing_secondary_email = True

    # Check for fields that are not editable. Marking them read-only causes them to be ignored, but we wish to 400.
    read_only_fields = set(update.keys()).intersection(
        AccountUserSerializer.get_read_only_fields() + AccountLegacyProfileSerializer.get_read_only_fields()
    )

    # Build up all field errors, whether read-only, validation, or email errors.
    field_errors = {}

    if read_only_fields:
        for read_only_field in read_only_fields:
            field_errors[read_only_field] = {
                "developer_message": u"This field is not editable via this API",
                "user_message": _(u"The '{field_name}' field cannot be edited.").format(field_name=read_only_field)
            }
            del update[read_only_field]

    user_serializer = AccountUserSerializer(existing_user, data=update)
    legacy_profile_serializer = AccountLegacyProfileSerializer(existing_user_profile, data=update)

    for serializer in user_serializer, legacy_profile_serializer:
        field_errors = add_serializer_errors(serializer, update, field_errors)

    # If the user asked to change email, validate it.
    if changing_email:
        try:
            student_views.validate_new_email(existing_user, new_email)
        except ValueError as err:
            field_errors["email"] = {
                "developer_message": u"Error thrown from validate_new_email: '{}'".format(text_type(err)),
                "user_message": text_type(err)
            }

        # Don't process with sending email to given new email, if it is already associated with
        # an account. User must see same success message with no error.
        # This is so that this endpoint cannot be used to determine if an email is valid or not.
        changing_email = new_email and not email_exists_or_retired(new_email)

    if changing_secondary_email:
        try:
            student_views.validate_secondary_email(account_recovery, update["secondary_email"])
        except ValueError as err:
            field_errors["secondary_email"] = {
                "developer_message": u"Error thrown from validate_secondary_email: '{}'".format(text_type(err)),
                "user_message": text_type(err)
            }
        else:
            account_recovery.update_recovery_email(update["secondary_email"])

    # If the user asked to change full name, validate it
    if changing_full_name:
        try:
            student_forms.validate_name(update['name'])
        except ValidationError as err:
            field_errors["name"] = {
                "developer_message": u"Error thrown from validate_name: '{}'".format(err.message),
                "user_message": err.message
            }

    # If we have encountered any validation errors, return them to the user.
    if field_errors:
        raise errors.AccountValidationError(field_errors)

    try:
        # If everything validated, go ahead and save the serializers.

        # We have not found a way using signals to get the language proficiency changes (grouped by user).
        # As a workaround, store old and new values here and emit them after save is complete.
        if "language_proficiencies" in update:
            old_language_proficiencies = list(existing_user_profile.language_proficiencies.values('code'))

        for serializer in user_serializer, legacy_profile_serializer:
            serializer.save()

        # if any exception is raised for user preference (i.e. account_privacy), the entire transaction for user account
        # patch is rolled back and the data is not saved
        if 'account_privacy' in update:
            update_user_preferences(
                requesting_user, {'account_privacy': update["account_privacy"]}, existing_user
            )

        if "language_proficiencies" in update:
            new_language_proficiencies = update["language_proficiencies"]
            emit_setting_changed_event(
                user=existing_user,
                db_table=existing_user_profile.language_proficiencies.model._meta.db_table,
                setting_name="language_proficiencies",
                old_value=old_language_proficiencies,
                new_value=new_language_proficiencies,
            )

        # If the name was changed, store information about the change operation. This is outside of the
        # serializer so that we can store who requested the change.
        if old_name:
            meta = existing_user_profile.get_meta()
            if 'old_names' not in meta:
                meta['old_names'] = []
            meta['old_names'].append([
                old_name,
                u"Name change requested through account API by {0}".format(requesting_user.username),
                datetime.datetime.now(UTC).isoformat()
            ])
            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 (AccountUpdateError, AccountValidationError) as err:
        raise err
    except Exception as err:
        raise AccountUpdateError(
            u"Error thrown when saving account updates: '{}'".format(text_type(err))
        )

    # And try to send the email change request if necessary.
    if changing_email:
        if not settings.FEATURES['ALLOW_EMAIL_ADDRESS_CHANGE']:
            raise AccountUpdateError(u"Email address changes have been disabled by the site operators.")
        try:
            student_views.do_email_change_request(existing_user, new_email)
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(text_type(err)),
                user_message=text_type(err)
            )
    if changing_secondary_email:
        try:
            student_views.do_email_change_request(
                user=existing_user,
                new_email=update["secondary_email"],
                secondary_email_change_request=True,
            )
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(text_type(err)),
                user_message=text_type(err)
            )
Пример #13
0
def update_account_settings(requesting_user, update, username=None):
    """Update user account information.

    Note:
        It is up to the caller of this method to enforce the contract that this method is only called
        with the user who made the request.

    Arguments:
        requesting_user (User): The user requesting to modify account information. Only the user with username
            'username' has permissions to modify account information.
        update (dict): The updated account field values.
        username (str): Optional username specifying which account should be updated. If not specified,
            `requesting_user.username` is assumed.

    Raises:
        errors.UserNotFound: no user with username `username` exists (or `requesting_user.username` if
            `username` is not specified)
        errors.UserNotAuthorized: the requesting_user does not have access to change the account
            associated with `username`
        errors.AccountValidationError: the update was not attempted because validation errors were found with
            the supplied update
        errors.AccountUpdateError: the update could not be completed. Note that if multiple fields are updated at the
            same time, some parts of the update may have been successful, even if an errors.AccountUpdateError is
            returned; in particular, the user account (not including e-mail address) may have successfully been updated,
            but then the e-mail change request, which is processed last, may throw an error.
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    if username is None:
        username = requesting_user.username

    existing_user, existing_user_profile = _get_user_and_profile(username)
    account_recovery = _get_account_recovery(existing_user)

    if requesting_user.username != username:
        raise errors.UserNotAuthorized()

    # If user has requested to change email, we must call the multi-step process to handle this.
    # It is not handled by the serializer (which considers email to be read-only).
    changing_email = False
    if "email" in update:
        changing_email = True
        new_email = update["email"]
        del update["email"]

    # If user has requested to change name, store old name because we must update associated metadata
    # after the save process is complete.
    changing_full_name = False
    old_name = None
    if "name" in update:
        changing_full_name = True
        old_name = existing_user_profile.name

    changing_secondary_email = False
    if "secondary_email" in update:
        changing_secondary_email = True

    # Check for fields that are not editable. Marking them read-only causes them to be ignored, but we wish to 400.
    read_only_fields = set(update.keys()).intersection(
        AccountUserSerializer.get_read_only_fields() +
        AccountLegacyProfileSerializer.get_read_only_fields())

    # Build up all field errors, whether read-only, validation, or email errors.
    field_errors = {}

    if read_only_fields:
        for read_only_field in read_only_fields:
            field_errors[read_only_field] = {
                "developer_message":
                u"This field is not editable via this API",
                "user_message":
                _(u"The '{field_name}' field cannot be edited.").format(
                    field_name=read_only_field)
            }
            del update[read_only_field]

    # custom field update code
    additional_fields = {"sch_org", "phone",
                         "organization_type"}.intersection(set(update.keys()))
    if additional_fields:
        field_error_methods = {
            'sch_org': get_sch_org_error,
            'phone': get_phone_error,
            'organization_type': get_organization_type_error
        }
        data = {}
        for key in additional_fields:
            message = field_error_methods[key](update[key])
            if message:
                field_errors[key] = {
                    'developer_message': message,
                    'user_message': message
                }
            else:
                data[key] = update[key]
        if data:
            instance = AdditionalRegistrationFields.objects.filter(
                user=existing_user).first()
            if instance:
                instance.__dict__.update(**data)
                instance.save()
            else:
                additional_fields_record = AdditionalRegistrationFields(**data)
                additional_fields_record.user = requesting_user
                try:
                    additional_fields_record.save()
                except OperationalError as e:
                    log.error(e, exc_info=True)

    user_serializer = AccountUserSerializer(existing_user, data=update)
    legacy_profile_serializer = AccountLegacyProfileSerializer(
        existing_user_profile, data=update)

    for serializer in user_serializer, legacy_profile_serializer:
        field_errors = add_serializer_errors(serializer, update, field_errors)

    # If the user asked to change email, validate it.
    if changing_email:
        try:
            student_views.validate_new_email(existing_user, new_email)
        except ValueError as err:
            field_errors["email"] = {
                "developer_message":
                u"Error thrown from validate_new_email: '{}'".format(
                    text_type(err)),
                "user_message":
                text_type(err)
            }

        # Don't process with sending email to given new email, if it is already associated with
        # an account. User must see same success message with no error.
        # This is so that this endpoint cannot be used to determine if an email is valid or not.
        changing_email = new_email and not email_exists_or_retired(new_email)

    if changing_secondary_email:
        try:
            student_views.validate_secondary_email(account_recovery,
                                                   update["secondary_email"])
        except ValueError as err:
            field_errors["secondary_email"] = {
                "developer_message":
                u"Error thrown from validate_secondary_email: '{}'".format(
                    text_type(err)),
                "user_message":
                text_type(err)
            }
        else:
            account_recovery.secondary_email = update["secondary_email"]
            account_recovery.save()

    # If the user asked to change full name, validate it
    if changing_full_name:
        try:
            student_forms.validate_name(update['name'])
        except ValidationError as err:
            field_errors["name"] = {
                "developer_message":
                u"Error thrown from validate_name: '{}'".format(err.message),
                "user_message":
                err.message
            }

    # If we have encountered any validation errors, return them to the user.
    if field_errors:
        raise errors.AccountValidationError(field_errors)

    try:
        # If everything validated, go ahead and save the serializers.

        # We have not found a way using signals to get the language proficiency changes (grouped by user).
        # As a workaround, store old and new values here and emit them after save is complete.
        if "language_proficiencies" in update:
            old_language_proficiencies = list(
                existing_user_profile.language_proficiencies.values('code'))

        for serializer in user_serializer, legacy_profile_serializer:
            serializer.save()

        # if any exception is raised for user preference (i.e. account_privacy), the entire transaction for user account
        # patch is rolled back and the data is not saved
        if 'account_privacy' in update:
            update_user_preferences(
                requesting_user,
                {'account_privacy': update["account_privacy"]}, existing_user)

        if "language_proficiencies" in update:
            new_language_proficiencies = update["language_proficiencies"]
            emit_setting_changed_event(
                user=existing_user,
                db_table=existing_user_profile.language_proficiencies.model.
                _meta.db_table,
                setting_name="language_proficiencies",
                old_value=old_language_proficiencies,
                new_value=new_language_proficiencies,
            )

        # If the name was changed, store information about the change operation. This is outside of the
        # serializer so that we can store who requested the change.
        if old_name:
            meta = existing_user_profile.get_meta()
            if 'old_names' not in meta:
                meta['old_names'] = []
            meta['old_names'].append([
                old_name,
                u"Name change requested through account API by {0}".format(
                    requesting_user.username),
                datetime.datetime.now(UTC).isoformat()
            ])
            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 (AccountUpdateError, AccountValidationError) as err:
        raise err
    except Exception as err:
        raise AccountUpdateError(
            u"Error thrown when saving account updates: '{}'".format(
                text_type(err)))

    # And try to send the email change request if necessary.
    if changing_email:
        if not settings.FEATURES['ALLOW_EMAIL_ADDRESS_CHANGE']:
            raise AccountUpdateError(
                u"Email address changes have been disabled by the site operators."
            )
        try:
            student_views.do_email_change_request(existing_user, new_email)
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(
                    text_type(err)),
                user_message=text_type(err))
    if changing_secondary_email:
        try:
            student_views.do_email_change_request(
                user=existing_user,
                new_email=update["secondary_email"],
                secondary_email_change_request=True,
            )
        except ValueError as err:
            raise AccountUpdateError(
                u"Error thrown from do_email_change_request: '{}'".format(
                    text_type(err)),
                user_message=text_type(err))