def get(self, request): """ --- # Swagger response_serializer: serializers.UserSerializer """ user = SupportRequest.target_user(request) if user.is_support_staff: sr = SupportRequest.get_current(self.request, as_obj=True) user = sr.user data = self.serializer_class(user).data if user.is_advisor: data['advisor'] = AdvisorSerializer(user.advisor).data elif user.is_client: data['client'] = ClientFieldSerializer(user.client).data elif user.is_supervisor: data['supervisor'] = SupervisorSerializer(user.supervisor).data elif user.is_authorised_representative: data['authorised_representative'] = AuthorisedRepresentativeSerializer(user.authorised_representative).data elif hasattr(user, 'invitation'): if user.invitation and user.invitation.status == EmailInvite.STATUS_ACCEPTED: data['invitation'] = InvitationSerializer(instance=user.invitation).data else: raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.") elif hasattr(user, 'firm_invitation'): if user.firm_invitation and user.firm_invitation.status == FirmEmailInvite.STATUS_ACCEPTED: data['invitation'] = FirmInvitationSerializer(instance=user.firm_invitation).data else: raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.") else: raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.") return Response(data)
def get(self, request): """ --- # Swagger response_serializer: serializers.UserSerializer """ user = SupportRequest.target_user(request) if user.is_support_staff: sr = SupportRequest.get_current(self.request, as_obj=True) user = sr.user data = self.serializer_class(user).data if user.is_advisor: role = 'advisor' data['advisor'] = AdvisorSerializer(user.advisor).data elif user.is_client: role = 'client' data['client'] = ClientFieldSerializer(user.client).data elif user.invitation and user.invitation.status == EmailInvite.STATUS_ACCEPTED: role = 'client' data['invitation'] = InvitationSerializer(instance=user.invitation).data else: raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.") data.update({'role': role}) return Response(data)
def awaiting_deposit(self, request, pk=None, deposit_pk=None, **kwargs): """ Gets or updates awaiting deposit by id. """ goal = self.get_object() sr_id = SupportRequest.get_current(request) try: transaction = Transaction.objects.get( Q(pk=deposit_pk), Q(status=Transaction.STATUS_AWAITING_APPROVAL), Q(to_goal=goal)) except ObjectDoesNotExist: raise ValidationError( 'Can not find specified awaiting one-time deposit') if request.method == 'GET': serializer = serializers.TransactionSerializer(transaction) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) elif request.method == 'PUT': client = goal.account.primary_owner site = get_current_site(request) serializer = serializers.TransactionSerializer(transaction, data=request.data) serializer.is_valid(raise_exception=True) if site.config().plaid_enabled: # make sure user has connected a plaid_user target_user = client.user plaid_user = getattr(target_user, 'plaid_user', False) if not plaid_user: logger.error( 'Deposit attempted by %s but no plaid_user found' % request.user) raise ValidationError('Client has no plaid_user linked') if not serializer.validated_data.get('account_id', False): logger.error( 'No plaid account_id sent with deposit request') raise ValidationError( 'No plaid account_id sent with deposit request') transaction = serializer.save() transaction.save() # TODO: Check whether other event log type is required for deposit update Event.GOAL_DEPOSIT.log('{} {}'.format(request.method, request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers)
def update(self, goal, validated_data): # Override the update method so we can make sure that only advisors can # update state, and only if the last state was ARCHIVE_REQUESTED request = self.context.get('request') new_state = validated_data.get('state', None) if new_state is not None and new_state != goal.state: if new_state != Goal.State.ACTIVE.value: raise PermissionDenied("The only state transition allowed is " "to {}".format(Goal.State.ACTIVE)) if goal.state != Goal.State.ARCHIVE_REQUESTED.value: raise PermissionDenied( "The only state transition allowed is from {}".format( Goal.State.ARCHIVE_REQUESTED)) sr = SupportRequest.get_current(request, as_obj=True) user, sr_id = (sr.user, sr.id) if sr else (request.user, None) # check helped user instead if support request is active if not user.is_advisor: raise PermissionDenied("Only an advisor can reactivate a goal") Event.REACTIVATE_GOAL.log('{} {}'.format(request.method, request.path), user=request.user, obj=goal, support_request_id=sr_id) # Finally, if we pass the validation, allow the update return super(GoalUpdateSerializer, self).update(goal, validated_data)
def selected_settings(self, request, pk=None, **kwargs): goal = self.get_object() if request.method == 'GET': serializer = serializers.GoalSettingSerializer( goal.selected_settings) return Response(serializer.data) with transaction.atomic(): # So both the log and change get committed. sr_id = SupportRequest.get_current(request) if request.method == 'POST': check_state(Goal.State(goal.state), Goal.State.ACTIVE) serializer = serializers.GoalSettingWritableSerializer( data=request.data) serializer.is_valid(raise_exception=True) event = Event.SET_SELECTED_SETTINGS.log( '{} {}'.format(self.request.method, self.request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) # Write any event memo for the event. All the details are wrapped by the serializer. serializer.write_memo(event) settings = serializer.save(goal=goal) # We use the read-only serializer to send the settings object, not the update serializer. serializer = serializers.GoalSettingSerializer(settings) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) elif request.method == 'PUT': check_state(Goal.State(goal.state), Goal.State.ACTIVE) settings = goal.selected_settings event = Event.UPDATE_SELECTED_SETTINGS.log( '{} {}'.format(self.request.method, self.request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) serializer = serializers.GoalSettingWritableSerializer( settings, data=request.data, partial=True) serializer.is_valid(raise_exception=True) # Write any event memo for the event. All the details are wrapped by the serializer. serializer.write_memo(event) settings = serializer.save(goal=goal) # We use the read-only serializer to send the settings object, not the update serializer. serializer = serializers.GoalSettingSerializer(settings) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers)
def deposit(self, request, pk=None, **kwargs): goal = self.get_object() check_state(Goal.State(goal.state), Goal.State.ACTIVE) sr_id = SupportRequest.get_current(request) Event.GOAL_DEPOSIT.log('{} {}'.format(request.method, request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) serializer = serializers.TransactionCreateSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(to_goal=goal, reason=Transaction.REASON_DEPOSIT) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def create(self, validated_data): request = self.context.get('request') sr = SupportRequest.get_current(request, as_obj=True) user, sr_id = (sr.user, sr.id) if sr else (request.user, None) roa = super(RecordOfAdviceSerializer, self).create(validated_data) Event.ROA_GENERATED.log('Record of Advice Generated', user=request.user, obj=roa, support_request_id=sr_id) goal = validated_data['goal'] client = goal.account.primary_owner Notify.ADVISOR_CREATE_ROA.send(actor=user, recipient=client.user, action_object=roa, target=goal) return roa
def archive(self, request, pk=None, **kwargs): """ Override this method as we don't want to actually delete the goal, just disable it. :param instance: The goal to disable :return: None """ goal = self.get_object() # If I'm an advisor or the goal is unsupervised, # archive the goal immediately. sr = SupportRequest.get_current(request, as_obj=True) # check helped user instead if support request is active user, sr_id = (sr.user, sr.id) if sr else (request.user, None) if not goal.account.supervised or user.is_advisor: check_state(Goal.State(goal.state), [ Goal.State.ACTIVE, Goal.State.ARCHIVE_REQUESTED, Goal.State.INACTIVE ]) Event.ARCHIVE_GOAL.log('{} {}'.format(request.method, request.path), user=request.user, obj=goal, support_request_id=sr_id) # Set the state to archive requested, # as the call to archive() requires it. goal.state = Goal.State.ARCHIVE_REQUESTED.value goal.archive() else: # I'm a client with a supervised goal, just change the status to # ARCHIVE_REQUESTED, and add a notification check_state(Goal.State(goal.state), [Goal.State.ACTIVE, Goal.State.INACTIVE]) Event.ARCHIVE_GOAL_REQUESTED.log('{} {}'.format( request.method, request.path), user=request.user, obj=goal, support_request_id=sr_id) # Flag the goal as archive requested. goal.state = Goal.State.ARCHIVE_REQUESTED.value # TODO: Add a notification to the advisor that the goal is archive requested. goal.save() return Response(serializers.GoalSerializer(goal).data)
def revert_selected(self, request, pk=None, **kwargs): """ Called to revert the current selected-settings to the approved-settings Returns a validation error if there is no approved-settings. """ goal = self.get_object() check_state(Goal.State(goal.state), Goal.State.ACTIVE) if not goal.approved_settings: raise ValidationError("No settings have yet been approved for " "this Goal, cannot revert to last approved.") sr_id = SupportRequest.get_current(request) Event.REVERT_SELECTED_SETTINGS.log('{} {}'.format( request.method, request.path), user=request.user, obj=goal, support_request_id=sr_id) goal.revert_selected() serializer = serializers.GoalSettingSerializer(goal.selected_settings) return Response(serializer.data)
def render(self, data, media_type=None, renderer_context=None): """ NB. be sure that settings.REST_FRAMEWORK contains: 'EXCEPTION_HANDLER': '...api_exception_handler', """ logger = logging.getLogger(__name__) wrapper = { 'version': '2', 'data': {}, 'meta': {}, } # move error to the root level if hasattr(data, 'get') and data.get('error'): wrapper['error'] = data['error'] del data['error'] if data is not None: wrapper['data'] = data try: response = renderer_context['response'] request = renderer_context['request'] if 200 <= response.status_code < 400: meta = {} session_expire = SessionExpire(request) meta['session_expires_on'] = session_expire.expire_time() sr = SupportRequest.get_current(request, as_obj=True) if sr: meta['support_request'] = { 'ticket': sr.ticket, 'user': UserSerializer(instance=sr.user).data, } wrapper['meta'] = meta except (TypeError, KeyError) as e: logger.error("Missing parameteres (%s)", e) return super(ApiRenderer, self).render(wrapper, media_type, renderer_context)
def approve_selected(self, request, pk=None, **kwargs): """ Called to make the currently selected settings approved by the advisor, ready to be activated next time the account is processed (rebalance). """ sr = SupportRequest.get_current(request, as_obj=True) # check helped user instead if support request is active user, sr_id = (sr.user, sr.id) if sr else (request.user, None) if not user.is_advisor: raise PermissionDenied('Only an advisor can approve selections.') goal = self.get_object() check_state(Goal.State(goal.state), Goal.State.ACTIVE) Event.APPROVE_SELECTED_SETTINGS.log('{} {}'.format( request.method, request.path), user=request.user, obj=goal, support_request_id=sr_id) goal.approve_selected() serializer = serializers.GoalSettingSerializer(goal.approved_settings) return Response(serializer.data)
def withdraw(self, request, pk=None, **kwargs): goal = self.get_object() check_state(Goal.State(goal.state), Goal.State.ACTIVE) sr_id = SupportRequest.get_current(request) Event.GOAL_WITHDRAWAL.log('{} {}'.format(request.method, request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) serializer = serializers.TransactionCreateSerializer(data=request.data) serializer.is_valid(raise_exception=True) # Make sure the total amount for the goal is larger than the pending withdrawal amount. if goal.current_balance + 0.0000001 < serializer.validated_data[ 'amount']: emsg = "Goal's current balance: {} is less than the desired withdrawal amount: {}" raise SystemConstraintError( emsg.format(goal.current_balance, serializer.validated_data['amount'])) serializer.save(from_goal=goal, reason=Transaction.REASON_WITHDRAWAL) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def put(self, request): """ --- # Swagger request_serializer: serializers.UserUpdateSerializer response_serializer: serializers.UserSerializer """ user = SupportRequest.target_user(request) if user.is_support_staff: sr = SupportRequest.get_current(self.request, as_obj=True) user = sr.user serializer = serializers.UserUpdateSerializer(user, data=request.data, partial=True, context={ 'request': request, }) serializer.is_valid(raise_exception=True) user = serializer.save() data = self.serializer_class(user).data if user.is_advisor: role = 'advisor' data['advisor'] = AdvisorSerializer(user.advisor).data elif user.is_client: role = 'client' # If the user wants to update client details, they do it through the specific client endpoint. data['client'] = ClientFieldSerializer(user.client).data else: raise PermissionDenied("User is not in the client or " "advisor groups.") data.update({'role': role}) return Response(data)
def is_support_staff(request): try: return SupportRequest.get_current(request) and \ request.user.is_support_staff except AttributeError: # anonymous users don't have `is_support_staff` pass
def withdraw(self, request, pk=None, **kwargs): goal = self.get_object() check_state(Goal.State(goal.state), Goal.State.ACTIVE) client = goal.account.primary_owner site = get_current_site(request) target_user = client.user sr_id = SupportRequest.get_current(request) if site.config().plaid_enabled: if not hasattr(target_user, 'invitation'): logger.error('Django user missing invitation') raise ValidationError( 'Client missing invitation, needed for withdrawals') else: if not hasattr(target_user.invitation, 'photo_verification'): logger.error( 'Django user missing invitation.photo_verification') raise ValidationError( 'Client missing invitation.photo_verification, needed for withdrawals' ) serializer = serializers.TransactionCreateSerializer(data=request.data) serializer.is_valid(raise_exception=True) # Make sure the total amount for the goal is larger than the pending withdrawal amount. if goal.current_balance + 0.0000001 < serializer.validated_data[ 'amount']: emsg = "Goal's current balance: {} is less than the desired withdrawal amount: {}" raise SystemConstraintError( emsg.format(goal.current_balance, serializer.validated_data['amount'])) if site.config().plaid_enabled: # make sure user has connected a plaid_user plaid_user = getattr(target_user, 'plaid_user', False) if not plaid_user: logger.error( 'Withdrawal attempted by %s but no plaid_user found' % target_user) raise ValidationError('Client has no plaid_user linked') if not serializer.validated_data.get('account_id', False): logger.error( 'No plaid account_id sent with withdrawal request') raise ValidationError( 'No plaid account_id sent with withdrawal request') transaction = serializer.save(from_goal=goal, reason=Transaction.REASON_WITHDRAWAL) if site.config().plaid_enabled: # execute transaction through stripe logger.info('Executing valid withdrawal request from %s for %s' % (target_user, serializer.validated_data['amount'])) stripe_token = get_stripe_account_token( target_user, serializer.validated_data['account_id']) try: transfer = execute_withdrawal( target_user, serializer.validated_data['amount'], stripe_token) if not transfer: logger.error( 'Failed to execute withdrawal, marking Transaction %s failed' % transaction) transaction.status = Transaction.STATUS_FAILED transaction.save() return Response('Failed to charge account', status=status.HTTP_400_BAD_REQUEST) transaction.stripe_id = transfer.id transaction.save() except Exception as e: logger.error( 'Failed to execute withdrawal, marking Transaction %s failed' % transaction) logger.error(e) transaction.status = Transaction.STATUS_FAILED transaction.save() return Response('Failed to charge account', status=status.HTTP_400_BAD_REQUEST) else: # manual transfer, goal cash balance updated directly transaction.execute() Event.GOAL_WITHDRAWAL.log('{} {}'.format(request.method, request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def deposit(self, request, pk=None, **kwargs): goal = self.get_object() sr_id = SupportRequest.get_current(request) Event.GOAL_DEPOSIT.log('{} {}'.format(request.method, request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) serializer = serializers.TransactionCreateSerializer(data=request.data) serializer.is_valid(raise_exception=True) client = goal.account.primary_owner site = get_current_site(request) if site.config().plaid_enabled: # make sure user has connected a plaid_user target_user = client.user plaid_user = getattr(target_user, 'plaid_user', False) if not plaid_user: logger.error( 'Deposit attempted by %s but no plaid_user found' % target_user) raise ValidationError('Client has no plaid_user linked') if not serializer.validated_data.get('account_id', False): logger.error('No plaid account_id sent with deposit request') raise ValidationError( 'No plaid account_id sent with deposit request') transaction = serializer.save(to_goal=goal, reason=Transaction.REASON_DEPOSIT) # check if goal is active if transaction.to_goal.state == Goal.State.ACTIVE.value and \ goal.approved_settings == goal.selected_settings: if site.config().plaid_enabled: # execute transaction through stripe if active logger.info('Executing valid deposit request from %s for %s' % (target_user, serializer.validated_data['amount'])) stripe_token = get_stripe_account_token( target_user, serializer.validated_data['account_id']) if stripe_token is None: logger.error('Failed to retrieve stripe_token for %s' % target_user) raise ValidationError('Failed to retrieve stripe_token') charge = execute_charge(serializer.validated_data['amount'], stripe_token) transaction.stripe_id = charge.id transaction.status = Transaction.STATUS_PENDING transaction.save() else: # manual transfer, goal cash balance updated directly transaction.execute() else: logger.info( 'Saving transaction %s with status awaiting approval, to_goal %s is not active' % (transaction, goal)) transaction.status = Transaction.STATUS_AWAITING_APPROVAL transaction.save() headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def selected_settings(self, request, pk=None, **kwargs): goal = self.get_object() if request.method == 'GET': serializer = serializers.GoalSettingSerializer( goal.selected_settings) return Response(serializer.data) sr_id = SupportRequest.get_current(request) data = request.data roa = None if request.method == 'POST': with transaction.atomic( ): # So both the log and change get committed. check_state(Goal.State(goal.state), [Goal.State.ACTIVE, Goal.State.INACTIVE]) # If this field is specified and it is an update from an advisor, we create a record of advice. if 'record_of_advice' in request.data: roa_data = data.pop('record_of_advice') roa_ser = serializers.RecordOfAdviceSerializer( data=roa_data) roa_ser.initial_data['goal'] = goal.id roa_ser.is_valid(raise_exception=True) roa = roa_ser.save() serializer = serializers.GoalSettingWritableSerializer( data=data, context={'request': request}) serializer.is_valid(raise_exception=True) event = Event.SET_SELECTED_SETTINGS.log( '{} {}'.format(self.request.method, self.request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) settings = serializer.save(goal=goal) if roa: roa.send_roa_generated_email() # We use the read-only serializer to send the settings object, not the update serializer. serializer = serializers.GoalSettingSerializer(settings) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) elif request.method == 'PUT': with transaction.atomic( ): # So both the log and change get committed. check_state(Goal.State(goal.state), [Goal.State.ACTIVE, Goal.State.INACTIVE]) # If this field is specified and it is an update from an advisor, we create a record of advice. if 'record_of_advice' in request.data: roa_data = data.pop('record_of_advice') roa_ser = serializers.RecordOfAdviceSerializer( data=roa_data, context={'request': request}) roa_ser.initial_data['goal'] = goal.id roa_ser.is_valid(raise_exception=True) roa = roa_ser.save() settings = goal.selected_settings event = Event.UPDATE_SELECTED_SETTINGS.log( '{} {}'.format(self.request.method, self.request.path), request.data, user=request.user, obj=goal, support_request_id=sr_id) serializer = serializers.GoalSettingWritableSerializer( settings, data=data, partial=True) serializer.is_valid(raise_exception=True) settings = serializer.save(goal=goal) if roa: roa.send_roa_generated_email() # We use the read-only serializer to send the settings object, not the update serializer. serializer = serializers.GoalSettingSerializer(settings) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers)