def test_calls_send_email_to_multiple_addresses(email_app, mandrill): with email_app.app_context(): mandrill.messages.send.return_value = [ {'_id': '123', 'email': '123'}] send_email( ["email_address1", "email_address2"], "body", "api_key", "subject", "from_email", "from_name", ["password-resets"] ) assert mandrill.messages.send.call_args[1]['message']['to'] == [ {'email': "email_address1", 'type': 'to'}, {'email': "email_address2", 'type': 'to'}, ] assert mandrill.messages.send.call_args[1]['message']['recipient_metadata'] == [ {'rcpt': "email_address1"}, {'rcpt': "email_address2"}, ]
def notify_users(email_api_key, brief): logger.info("Notifying users about brief ID: {brief_id} - '{brief_title}'", extra={'brief_title': brief['title'], 'brief_id': brief['id']}) for user in brief['users']: try: email_body = render_html('email_templates/requirements_closed.html', data={ 'brief_id': brief['id'], 'brief_title': brief['title'], 'lot_slug': brief['lotSlug'], 'framework_slug': brief['frameworkSlug'] }) send_email( [user['emailAddress'] for user in brief['users'] if user['active']], email_body, email_api_key, u'Next steps for your ‘{}’ requirements'.format(brief['title']), '*****@*****.**', 'Digital Marketplace Admin', ['requirements-closed'], metadata={'brief_id': brief['id']}, logger=logger ) return True except MandrillException as e: logger.error( "Email failed to send for brief_id: {brief_id}", extra={'error': e, 'brief_id': brief['id']} ) return False
def send_new_opportunity_email_to_sellers(brief_json, brief_url): to_email_addresses = [] if brief_json.get('sellerEmail'): to_email_addresses.append(brief_json['sellerEmail']) if brief_json.get('sellerEmailList'): to_email_addresses += brief_json['sellerEmailList'] if to_email_addresses: email_body = render_template( 'emails/seller_new_opportunity.html', brief=brief_json, brief_url=brief_url ) for to_email_address in to_email_addresses: # Send emails individually rather than sending to a list of emails try: send_email( to_email_address, email_body, current_app.config['SELLER_NEW_OPPORTUNITY_EMAIL_SUBJECT'], current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_SUPPORT_NAME'], ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'seller new opportunity email failed to send. ' 'error {error}', extra={ 'error': six.text_type(e), }) abort(503, response='Failed to send seller new opportunity email.')
def send_buyer_account_activation_email(name, email_address, token): token = generate_buyer_creation_token(name=name, email_address=email_address) url = url_for('main.create_buyer_account', token=token, _external=True) email_body = render_template('emails/create_buyer_user_email.html', url=url) try: send_email( email_address, email_body, current_app.config['CREATE_USER_SUBJECT'], current_app.config['RESET_PASSWORD_EMAIL_FROM'], current_app.config['RESET_PASSWORD_EMAIL_NAME'], ) session['email_sent_to'] = email_address except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'buyercreate.fail: Create user email failed to send. ' 'error {error} email_hash {email_hash}', extra={ 'error': six.text_type(e), 'email_hash': hash_email(email_address) }) abort(503, response='Failed to send user creation email.')
def send_thank_you_email_to_responders(brief, brief_response, brief_response_url): ess = brief.get('essentialRequirements', []) nth = brief.get('niceToHaveRequirements', []) ess = zip(ess, brief_response.get('essentialRequirements', [])) nth = zip(nth, brief_response.get('niceToHaveRequirements', [])) to_email_address = brief_response['respondToEmailAddress'] email_body = render_template( 'emails/brief_response_submitted.html', brief=brief, essential_requirements=ess, nice_to_have_requirements=nth, brief_response=brief_response, brief_response_url=brief_response_url, frontend_address=current_app.config['FRONTEND_ADDRESS'] ) try: send_email( to_email_address, email_body, 'We\'ve received your application', current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_SUPPORT_NAME'], ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'seller response received email failed to send. ' 'error {error}', extra={ 'error': six.text_type(e), }) abort(503, response='Failed to send seller response received email.')
def test_calls_send_email_with_correct_params(email_app, email_client): with email_app.app_context(): send_email( "email_address", "body", "subject", "from_email", "from_name", ) email_client.send_email.assert_called_once_with( ReplyToAddresses=['from_email'], Message={ 'Body': { 'Html': { 'Charset': 'UTF-8', 'Data': 'body' } }, 'Subject': { 'Charset': 'UTF-8', 'Data': 'subject' } }, Destination={ 'ToAddresses': ['email_address'], 'BccAddresses': [TEST_ARCHIVE_ADDRESS] }, Source='from_name <from_email>', ReturnPath=TEST_RETURN_ADDRESS)
def send_brief_clarification_question(data_api_client, brief, clarification_question): # Email the question to brief owners email_body = render_template( "emails/brief_clarification_question.html", brief_id=brief['id'], brief_name=brief['title'], publish_by_date=brief['clarificationQuestionsPublishedBy'], framework_slug=brief['frameworkSlug'], lot_slug=brief['lotSlug'], message=clarification_question, ) try: send_email( to_email_addresses=get_brief_user_emails(brief), email_body=email_body, api_key=current_app.config['DM_MANDRILL_API_KEY'], subject=u"You’ve received a new supplier question about ‘{}’".format(brief['title']), from_email=current_app.config['CLARIFICATION_EMAIL_FROM'], from_name="{} Supplier".format(brief['frameworkName']), tags=["brief-clarification-question"] ) except MandrillException as e: current_app.logger.error( "Brief question email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}", extra={'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'brief_id': brief['id']} ) abort(503, "Clarification question email failed to send") data_api_client.create_audit_event( audit_type=AuditTypes.send_clarification_question, user=current_user.email_address, object_type="briefs", object_id=brief['id'], data={"question": clarification_question, "briefId": brief['id']}) # Send the supplier a copy of the question supplier_email_body = render_template( "emails/brief_clarification_question_confirmation.html", brief_id=brief['id'], brief_name=brief['title'], framework_slug=brief['frameworkSlug'], message=clarification_question ) try: send_email( to_email_addresses=[current_user.email_address], email_body=supplier_email_body, api_key=current_app.config['DM_MANDRILL_API_KEY'], subject=u"Your question about ‘{}’".format(brief['title']), from_email=current_app.config['CLARIFICATION_EMAIL_FROM'], from_name=current_app.config['CLARIFICATION_EMAIL_NAME'], tags=["brief-clarification-question-confirmation"] ) except MandrillException as e: current_app.logger.error( "Brief question supplier email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}", extra={'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'brief_id': brief['id']} )
def submit_buyer_invite_request(): form = auth_forms.BuyerInviteRequestForm(request.form) if not form.validate(): return render_template_with_csrf('auth/buyer-invite-request.html', status_code=400, form=form) token = generate_buyer_creation_token(**form.data) invite_url = url_for('.send_buyer_invite', token=token, _external=True) email_body = render_template( 'emails/buyer_account_invite_request_email.html', form=form, invite_url=invite_url) try: send_email( current_app.config['BUYER_INVITE_REQUEST_ADMIN_EMAIL'], email_body, current_app.config['BUYER_INVITE_REQUEST_SUBJECT'], current_app.config['BUYER_INVITE_REQUEST_EMAIL_FROM'], current_app.config['BUYER_INVITE_REQUEST_EMAIL_NAME'], ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'buyerinvite.fail: Buyer account invite request email failed to send. ' 'error {error} invite email_hash {email_hash}', extra={ 'error': six.text_type(e), 'email_hash': hash_email(form.email_address.data) }) abort(503, response='Failed to send buyer account invite request email.') email_body = render_template( 'emails/buyer_account_invite_manager_confirmation.html', form=form) try: send_email( form.manager_email.data, email_body, current_app.config['BUYER_INVITE_MANAGER_CONFIRMATION_SUBJECT'], current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['BUYER_INVITE_MANAGER_CONFIRMATION_NAME'], ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'buyerinvite.fail: Buyer account invite request manager email failed to send. ' 'error {error} invite email_hash {email_hash} manager email_hash {manager_email_hash}', extra={ 'error': six.text_type(e), 'email_hash': hash_email(form.email_address.data), 'manager_email_hash': hash_email(form.manager_email.data) }) abort(503, response= 'Failed to send buyer account invite request manager email.') return render_template('auth/buyer-invite-request-complete.html')
def send_invite_user(): form = EmailAddressForm(request.form) if form.validate(): email_address = form.email_address.data token = generate_supplier_invitation_token( name='', supplier_code=current_user.supplier_code, supplier_name=current_user.supplier_name, email_address=email_address ) url = url_for('main.create_user', token=token, _external=True) email_body = render_template( 'emails/invite_user_email.html', url=url, user=current_user.name, supplier=current_user.supplier_name, generic_contact_email=current_app.config['GENERIC_CONTACT_EMAIL']) try: send_email( email_address, email_body, 'Invitation to join {} as a team member'.format( current_user .supplier_name .encode("ascii", "ignore") .decode('ascii') ), current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'] ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'Invitation email failed to send. ' 'error {error} supplier_code {supplier_code} email_hash {email_hash}', extra={'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address)}) abort(503, 'Failed to send user invite reset') data_api_client.create_audit_event( audit_type=AuditTypes.invite_user, user=current_user.email_address, object_type='suppliers', object_id=current_user.supplier_code, data={'invitedEmail': email_address}, ) form.email_address.data = None return render_template_with_csrf( 'auth/submit_email_address.html', form=form, invited_user=email_address) else: return render_template_with_csrf( 'auth/submit_email_address.html', status_code=400, form=form)
def notify_team(subject, body, more_info_url=None): """ Generic routine for making simple notifications to the Marketplace team. Notification messages should be very simple so that they're compatible with a variety of backends. """ # ensure strings can be encoded as ascii only body = body.encode("ascii", "ignore").decode('ascii') subject = subject.encode("ascii", "ignore").decode('ascii') if current_app.config.get('DM_TEAM_SLACK_WEBHOOK', None): slack_body = slack_escape(body) if more_info_url: slack_body += '\n' + more_info_url data = { 'attachments': [{ 'title': subject, 'text': slack_body, 'fallback': '{} - {} {}'.format(subject, body, more_info_url), }], 'username': '******', } response = requests.post(current_app.config['DM_TEAM_SLACK_WEBHOOK'], json=data) if response.status_code != 200: msg = 'Failed to send notification to Slack channel: {} - {}'.format( response.status_code, response.text) current_app.logger.error(msg) if current_app.config.get('DM_TEAM_EMAIL', None): email_body = render_template_string( '<p>{{ body }}</p>{% if more_info_url %}<a href="{{ more_info_url }}">More info</a>{% endif %}', body=body, more_info_url=more_info_url) try: send_email( current_app.config['DM_TEAM_EMAIL'], email_body, subject, current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_ADMIN_NAME'], ) except EmailError as error: try: msg = error.message except AttributeError: msg = str(error) rollbar.report_exc_info() current_app.logger.error( 'Failed to send notification email: {}'.format(msg))
def invite_user_to_application(application_id): json_payload = request.get_json(True) email_address = json_payload.get('email') name = json_payload.get('name') application = data_api_client.get_application(application_id) if not application: return abort(404) user_json = data_api_client.get_user(email_address=email_address) if user_json: return abort(400) token_data = {'id': application_id, 'name': name, 'email_address': email_address} token = generate_token(token_data, current_app.config['SECRET_KEY'], current_app.config['INVITE_EMAIL_SALT']) try: token = token.decode() except AttributeError: pass url = '{}://{}/{}/{}'.format( current_app.config['DM_HTTP_PROTO'], current_app.config['DM_MAIN_SERVER_NAME'], current_app.config['CREATE_APPLICANT_PATH'], format(token) ) email_body = render_template( 'emails/invite_user_application_email.html', url=url, supplier=application['application']['name'], name=name, ) try: send_email( email_address, email_body, current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'], ) except EmailError as e: current_app.logger.error( 'Invitation email failed to send error {} to {} supplier {} supplier code {} '.format( str(e), email_address, current_user.supplier_name, current_user.supplier_code) ) abort(503, "Failed to send user invite reset") return jsonify(success=True), 200
def send_invite_user(): form = EmailAddressForm() if form.validate_on_submit(): token = generate_token( { "supplier_id": current_user.supplier_id, "supplier_name": current_user.supplier_name, "email_address": form.email_address.data }, current_app.config['SHARED_EMAIL_KEY'], current_app.config['INVITE_EMAIL_SALT'] ) url = url_for('main.create_user', encoded_token=token, _external=True) email_body = render_template( "emails/invite_user_email.html", url=url, user=current_user.name, supplier=current_user.supplier_name) try: send_email( form.email_address.data, email_body, current_app.config['DM_MANDRILL_API_KEY'], current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'], ["user-invite"] ) except MandrillException as e: current_app.logger.error( "Invitation email failed to send. " "error {error} supplier_id {supplier_id} email_hash {email_hash}", extra={'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'email_hash': hash_email(current_user.email_address)}) abort(503, "Failed to send user invite reset") data_api_client.create_audit_event( audit_type=AuditTypes.invite_user, user=current_user.email_address, object_type='suppliers', object_id=current_user.supplier_id, data={'invitedEmail': form.email_address.data}, ) flash('user_invited', 'success') return redirect(url_for('.list_users')) else: return render_template( "auth/submit_email_address.html", form=form), 400
def authorise_application(id): application = data_api_client.get_application(id) if not can_user_view_application(application): abort(403, 'Not authorised to access application') if is_application_submitted(application): return redirect(url_for('.submit_application', id=id)) application = application['application'] url = url_for('main.render_application', id=id, step='submit', _external=True) user_json = data_api_client.get_user(email_address=application['email']) template = 'emails/create_authorise_email_has_account.html' if not user_json: token_data = { 'id': id, 'name': application['representative'], 'email_address': application['email'] } token = generate_application_invitation_token(token_data) url = url_for('main.render_create_application', token=token, _external=True) template = 'emails/create_authorise_email_no_account.html' email_body = render_template( template, url=url, name=application['representative'], business_name=application['name'], ) try: send_email(application['email'], email_body, current_app.config['AUTHREP_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME']) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'Authorisation email failed to send. ' 'error {error}', extra={'error': six.text_type(e)}) abort(503, 'Failed to send user invite reset') return render_template('suppliers/authorisation_submitted.html', name=application['representative'], email_address=application['email'], subject=current_app.config['AUTHREP_EMAIL_SUBJECT'])
def test_calls_send_email_to_multiple_addresses(email_app, email_client): with email_app.app_context(): send_email( ["email_address1", "email_address2"], "body", "subject", "from_email", "from_name", ) assert email_client.send_email.call_args[1]['Destination']['ToAddresses'] == [ "email_address1", "email_address2", ]
def test_should_throw_exception_if_email_client_fails(email_app, email_client): with email_app.app_context(): email_client.send_email.side_effect = ClientError( {'Error': {'Message': "this is an error"}}, "" ) with pytest.raises(EmailError): send_email( "email_address", "body", "subject", "from_email", "from_name", )
def invite_user_to_application(application_id): json_payload = request.get_json(True) email_address = json_payload.get('email') name = json_payload.get('name') application = data_api_client.get_application(application_id) if not application: return abort(404) user_json = data_api_client.get_user(email_address=email_address) if user_json: return abort(400) token_data = {'id': application_id, 'name': name, 'email_address': email_address} token = generate_token(token_data, current_app.config['SECRET_KEY'], current_app.config['INVITE_EMAIL_SALT']) url = '{}://{}/{}/{}'.format( current_app.config['DM_HTTP_PROTO'], current_app.config['DM_MAIN_SERVER_NAME'], current_app.config['CREATE_APPLICANT_PATH'], format(token) ) email_body = render_template( 'emails/invite_user_application_email.html', url=url, supplier=application['application']['name'], name=name, ) try: send_email( email_address, email_body, current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'], ) except EmailError as e: current_app.logger.error( 'Invitation email failed to send error {} to {} supplier {} supplier code {} '.format( str(e), email_address, current_user.supplier_name, current_user.supplier_code) ) abort(503, "Failed to send user invite reset") return jsonify(success=True), 200
def notify_team(subject, body, more_info_url=None): """ Generic routine for making simple notifications to the Marketplace team. Notification messages should be very simple so that they're compatible with a variety of backends. """ # ensure strings can be encoded as ascii only body = body.encode("ascii", "ignore").decode('ascii') subject = subject.encode("ascii", "ignore").decode('ascii') if current_app.config.get('DM_TEAM_SLACK_WEBHOOK', None): slack_body = slack_escape(body) if more_info_url: slack_body += '\n' + more_info_url data = { 'attachments': [{ 'title': subject, 'text': slack_body, 'fallback': '{} - {} {}'.format(subject, body, more_info_url), }], 'username': '******', } response = requests.post( current_app.config['DM_TEAM_SLACK_WEBHOOK'], json=data ) if response.status_code != 200: msg = 'Failed to send notification to Slack channel: {} - {}'.format(response.status_code, response.text) current_app.logger.error(msg) if current_app.config.get('DM_TEAM_EMAIL', None): email_body = render_template_string( '<p>{{ body }}</p>{% if more_info_url %}<a href="{{ more_info_url }}">More info</a>{% endif %}', body=body, more_info_url=more_info_url ) try: send_email( current_app.config['DM_TEAM_EMAIL'], email_body, subject, current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_ADMIN_NAME'], ) except EmailError as e: try: msg = e.message except AttributeError: msg = str(e) rollbar.report_exc_info() current_app.logger.error('Failed to send notification email: {}'.format(msg))
def send_invite_user(): form = EmailAddressForm(request.form) if form.validate(): token = generate_supplier_invitation_token( name='', supplier_code=current_user.supplier_code, supplier_name=current_user.supplier_name, email_address=form.email_address.data ) url = url_for('main.create_user', token=token, _external=True) email_body = render_template( 'emails/invite_user_email.html', url=url, user=current_user.name, supplier=current_user.supplier_name) try: send_email( form.email_address.data, email_body, current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'] ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'Invitation email failed to send. ' 'error {error} supplier_code {supplier_code} email_hash {email_hash}', extra={'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address)}) abort(503, 'Failed to send user invite reset') data_api_client.create_audit_event( audit_type=AuditTypes.invite_user, user=current_user.email_address, object_type='suppliers', object_id=current_user.supplier_code, data={'invitedEmail': form.email_address.data}, ) flash('user_invited', 'success') return redirect(url_for('.list_users')) else: return render_template_with_csrf( 'auth/submit_email_address.html', status_code=400, form=form)
def authorise_application(id): application = data_api_client.get_application(id) if not can_user_view_application(application): abort(403, 'Not authorised to access application') if is_application_submitted(application): return redirect(url_for('.submit_application', id=id)) application = application['application'] url = url_for('main.render_application', id=id, step='submit', _external=True) user_json = data_api_client.get_user(email_address=application['email']) template = 'emails/create_authorise_email_has_account.html' if not user_json: token_data = {'id': id, 'name': application['representative'], 'email_address': application['email']} token = generate_application_invitation_token(token_data) url = url_for('main.render_create_application', token=token, _external=True) template = 'emails/create_authorise_email_no_account.html' email_body = render_template( template, url=url, name=application['representative'], business_name=application['name'], ) try: send_email( application['email'], email_body, current_app.config['AUTHREP_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'] ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'Authorisation email failed to send. ' 'error {error}', extra={'error': six.text_type(e)} ) abort(503, 'Failed to send user invite reset') return render_template('suppliers/authorisation_submitted.html', name=application['representative'], email_address=application['email'], subject=current_app.config['AUTHREP_EMAIL_SUBJECT'])
def test_calls_send_email_missing_config(email_app_missing_config, email_client): with email_app_missing_config.app_context(): send_email( "email_address", "body", "subject", "from_email", "from_name", ) email_client.send_email.assert_called_once_with( ReplyToAddresses=['from_email'], Message={'Body': {'Html': {'Charset': 'UTF-8', 'Data': b'body'}}, 'Subject': {'Charset': 'UTF-8', 'Data': b'subject'}}, Destination={'ToAddresses': ['email_address'], 'BccAddresses': [TEST_ARCHIVE_ADDRESS]}, Source=u'from_name <from_email>', ReturnPath='from_email' )
def test_should_throw_exception_if_mandrill_fails(email_app, mandrill): with email_app.app_context(): mandrill.messages.send.side_effect = Error("this is an error") try: send_email( "email_address", "body", "api_key", "subject", "from_email", "from_name", ["password-resets"] ) except MandrillException as e: assert str(e) == "this is an error"
def test_calls_send_email_with_alternative_reply_to(email_app, email_client): with email_app.app_context(): send_email( "email_address", "body", "subject", "from_email", "from_name", reply_to="reply_address" ) email_client.send_email.assert_called_once_with( ReplyToAddresses=['reply_address'], Message={'Body': {'Html': {'Charset': 'UTF-8', 'Data': b'body'}}, 'Subject': {'Charset': 'UTF-8', 'Data': b'subject'}}, Destination={'ToAddresses': ['email_address'], 'BccAddresses': [TEST_ARCHIVE_ADDRESS]}, Source=u'from_name <from_email>', ReturnPath=TEST_RETURN_ADDRESS )
def send_buyer_onboarding_email(name, email_address): email_body = render_template('emails/buyer_onboarding.html', name=name) try: send_email( email_address, email_body, 'Welcome to the Digital Marketplace', current_app.config['RESET_PASSWORD_EMAIL_FROM'], current_app.config['RESET_PASSWORD_EMAIL_NAME'], ) session['email_sent_to'] = email_address except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'buyeronboarding.fail: Buyer onboarding email failed to send. ' 'error {error} email_hash {email_hash}', extra={ 'error': six.text_type(e), 'email_hash': hash_email(email_address)}) abort(503, response='Failed to send buyer onboarding email.')
def send_supplier_invite(name, email_address, supplier_code, supplier_name): """Send invite email to new supplier from Marketplace admin and record in API's log. Raises EmailError if failed to send, or HTTPError if logging failed. """ token = generate_supplier_invitation_token(name, email_address, supplier_code, supplier_name) activation_url = url_for('main.create_user', token=token, _external=True, _scheme=current_app.config['DM_HTTP_PROTO']) subject = current_app.config['NEW_SUPPLIER_INVITE_SUBJECT'] email_body = render_template('emails/new_supplier_invite_email.html', subject=subject, name=name, activation_url=activation_url) send_email(email_address, email_body, subject, current_app.config['DM_GENERIC_NOREPLY_EMAIL'], current_app.config['DM_GENERIC_ADMIN_NAME']) data_api_client.record_supplier_invite(supplier_code=supplier_code, email_address=email_address)
def send_seller_signup_email(): user = from_response(request) if user.get('user_type') != 'seller': return abort(400) fields = ['name', 'email_address'] errors = validate_form_data(user, fields) if errors: return start_seller_signup(user, errors) duplicate = data_api_client.req.users().checkduplicates().post(data={"email_address": user['email_address']}) if duplicate.get('duplicate'): return render_template('auth/seller-signup-email-sent.html', email_address=user['email_address']) token = generate_application_invitation_token(user) url = url_for('main.render_create_application', token=token, _external=True) email_body = render_template( 'emails/create_seller_user_email.html', url=url, user=user, ) try: send_email( user['email_address'], email_body, current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'] ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'Invitation email failed to send. ' 'error {error}', extra={'error': six.text_type(e)} ) abort(503, 'Failed to send user invite reset') return render_template('auth/seller-signup-email-sent.html', email_address=user['email_address'])
def send_buyer_account_activation_email(name, email_address, token): token = generate_buyer_creation_token(name=name, email_address=email_address) url = url_for('main.create_buyer_account', token=token, _external=True) email_body = render_template('emails/create_buyer_user_email.html', url=url) try: send_email( email_address, email_body, current_app.config['CREATE_USER_SUBJECT'], current_app.config['RESET_PASSWORD_EMAIL_FROM'], current_app.config['RESET_PASSWORD_EMAIL_NAME'], ) session['email_sent_to'] = email_address except EmailError as e: rollbar.report_exc_info() current_app.logger.error( 'buyercreate.fail: Create user email failed to send. ' 'error {error} email_hash {email_hash}', extra={ 'error': six.text_type(e), 'email_hash': hash_email(email_address)}) abort(503, response='Failed to send user creation email.')
def test_calls_send_email_with_correct_params(email_app, mandrill): with email_app.app_context(): mandrill.messages.send.return_value = [ {'_id': '123', 'email': '123'}] expected_call = { 'html': "body", 'subject': "subject", 'from_email': "from_email", 'from_name': "from_name", 'to': [{ 'email': "email_address", 'type': 'to' }], 'important': False, 'track_opens': False, 'track_clicks': False, 'auto_text': True, 'tags': ['password-resets'], 'headers': {'Reply-To': "from_email"}, # noqa 'preserve_recipients': False, 'recipient_metadata': [{ 'rcpt': "email_address" }] } send_email( "email_address", "body", "api_key", "subject", "from_email", "from_name", ["password-resets"] ) mandrill.messages.send.assert_called_once_with(message=expected_call, async=True)
def send_brief_clarification_question(data_api_client, brief, clarification_question): # Email the question to brief owners subject = u"You received a new question for ‘{}’".format(brief['title']) email_body = render_template( "emails/brief_clarification_question.html", brief_id=brief['id'], brief_name=brief['title'], publish_by_date=brief['clarificationQuestionsPublishedBy'], framework_slug=brief['frameworkSlug'], lot_slug=brief['lotSlug'], message=clarification_question, frontend_address=current_app.config['FRONTEND_ADDRESS'], supplier_name=current_user.supplier_name) try: send_email(to_email_addresses=get_brief_user_emails(brief), email_body=email_body, subject=subject, from_email=current_app.config['CLARIFICATION_EMAIL_FROM'], from_name="{} Supplier".format(brief['frameworkName'])) except EmailError as e: current_app.logger.error( "Brief question email failed to send. error={error} supplier_code={supplier_code} brief_id={brief_id}", extra={ 'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'brief_id': brief['id'] }) abort(503, "Clarification question email failed to send") data_api_client.create_audit_event( audit_type=AuditTypes.send_clarification_question, user=current_user.email_address, object_type="briefs", object_id=brief['id'], data={ "question": clarification_question, "briefId": brief['id'] }) # Send the supplier a copy of the question supplier_email_body = render_template( "emails/brief_clarification_question_confirmation.html", brief_id=brief['id'], brief_name=brief['title'], message=clarification_question, supplier_name=current_user.name, frontend_address=current_app.config['FRONTEND_ADDRESS'], brief_organisation=brief['organisation']) try: send_email( to_email_addresses=[current_user.email_address], email_body=supplier_email_body, subject=u"You submitted a question for {} ({}) successfully". format(brief['title'], brief['id']), from_email=current_app.config['CLARIFICATION_EMAIL_FROM'], from_name=current_app.config['CLARIFICATION_EMAIL_NAME']) except EmailError as e: current_app.logger.error( 'Brief question supplier email failed to send. error={error} supplier_code={supplier_code} brief_id={brief_id}', # noqa extra={ 'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'brief_id': brief['id'] })
def framework_dashboard(framework_slug): framework = get_framework(data_api_client, framework_slug) if request.method == 'POST': register_interest_in_framework(data_api_client, framework_slug) supplier_users = data_api_client.find_users( supplier_id=current_user.supplier_id) try: email_body = render_template( 'emails/{}_application_started.html'.format(framework_slug)) send_email([ user['emailAddress'] for user in supplier_users['users'] if user['active'] ], email_body, current_app.config['DM_MANDRILL_API_KEY'], 'You have started your {} application'.format( framework['name']), current_app.config['CLARIFICATION_EMAIL_FROM'], current_app.config['CLARIFICATION_EMAIL_NAME'], ['{}-application-started'.format(framework_slug)]) except MandrillException as e: current_app.logger.error( "Application started email failed to send: {error}, supplier_id: {supplier_id}", extra={ 'error': six.text_type(e), 'supplier_id': current_user.supplier_id }) drafts, complete_drafts = get_drafts(data_api_client, framework_slug) supplier_framework_info = get_supplier_framework_info( data_api_client, framework_slug) declaration_status = get_declaration_status_from_info( supplier_framework_info) supplier_is_on_framework = get_supplier_on_framework_from_info( supplier_framework_info) # Do not show a framework dashboard for earlier G-Cloud iterations if declaration_status == 'unstarted' and framework['status'] == 'live': abort(404) key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list( framework_slug, load_timestamps=True) key_list.reverse() first_page = content_loader.get_manifest( framework_slug, 'declaration').get_next_editable_section_id() # filenames supplier_pack_filename = '{}-supplier-pack.zip'.format(framework_slug) result_letter_filename = RESULT_LETTER_FILENAME countersigned_agreement_file = None if countersigned_framework_agreement_exists_in_bucket( framework_slug, current_app.config['DM_AGREEMENTS_BUCKET']): countersigned_agreement_file = COUNTERSIGNED_AGREEMENT_FILENAME return render_template( "frameworks/dashboard.html", application_made=supplier_is_on_framework or (len(complete_drafts) > 0 and declaration_status == 'complete'), completed_lots=[ { 'name': lot['name'], 'complete_count': count_drafts_by_lot(complete_drafts, lot['slug']), 'one_service_limit': lot['oneServiceLimit'], 'unit': 'lab' if framework['slug'] == 'digital-outcomes-and-specialists' else 'service', 'unit_plural': 'labs' if framework['slug'] == 'digital-outcomes-and-specialists' else 'service' # TODO: ^ make this dynamic, eg, lab, service, unit } for lot in framework['lots'] if count_drafts_by_lot(complete_drafts, lot['slug']) ], counts={ "draft": len(drafts), "complete": len(complete_drafts) }, dates=content_loader.get_message(framework_slug, 'dates'), declaration_status=declaration_status, first_page_of_declaration=first_page, framework=framework, last_modified={ 'supplier_pack': get_last_modified_from_first_matching_file( key_list, framework_slug, "communications/{}".format(supplier_pack_filename)), 'supplier_updates': get_last_modified_from_first_matching_file( key_list, framework_slug, "communications/updates/") }, supplier_is_on_framework=supplier_is_on_framework, supplier_pack_filename=supplier_pack_filename, result_letter_filename=result_letter_filename, countersigned_agreement_file=countersigned_agreement_file), 200
def submit_company_summary(): required_fields = [ "email_address", "phone_number", "contact_name", "duns_number", "company_name", "account_email_address" ] missing_fields = [field for field in required_fields if field not in session] if not missing_fields: supplier = { "name": session["company_name"], "dunsNumber": str(session["duns_number"]), "contacts": [{ "email": session["email_address"], "phoneNumber": session["phone_number"], "contactName": session["contact_name"] }] } if session.get("companies_house_number", None): supplier["companiesHouseNumber"] = session.get("companies_house_number") account_email_address = session.get("account_email_address", None) supplier = data_api_client.create_supplier(supplier)['supplier'] session.clear() session['email_company_name'] = supplier['name'] session['email_supplier_code'] = supplier['id'] token = generate_supplier_invitation_token( name='', email_address=account_email_address, supplier_code=session['email_supplier_code'], supplier_name=session['email_company_name'] ) url = url_for('main.create_user', token=token, _external=True) email_body = render_template( "emails/create_user_email.html", company_name=session['email_company_name'], url=url ) try: send_email( account_email_address, email_body, current_app.config['CREATE_USER_SUBJECT'], current_app.config['RESET_PASSWORD_EMAIL_FROM'], current_app.config['RESET_PASSWORD_EMAIL_NAME'], ["user-creation"] ) session['email_sent_to'] = account_email_address except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "suppliercreate.fail: Create user email failed to send. " "error {error} supplier_code {supplier_code} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'supplier_code': session['email_supplier_code'], 'email_hash': hash_email(account_email_address)}) abort(503, "Failed to send user creation email") data_api_client.create_audit_event( audit_type=AuditTypes.invite_user, object_type='suppliers', object_id=session['email_supplier_code'], data={'invitedEmail': account_email_address}) return redirect(url_for('.create_your_account_complete'), 302) else: return render_template_with_csrf( "suppliers/company_summary.html", status_code=400, missing_fields=missing_fields )
def contract_review(framework_slug): framework = get_framework(data_api_client, framework_slug) supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(data_api_client, framework_slug) agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) signature_page = get_most_recently_uploaded_agreement_file_or_none(agreements_bucket, framework_slug) # if supplier_framework doesn't have a name or a role or the agreement file, then 404 if not ( supplier_framework['agreementDetails'] and supplier_framework['agreementDetails'].get('signerName') and supplier_framework['agreementDetails'].get('signerRole') and signature_page ): abort(404) form = ContractReviewForm(request.form) form_errors = None if request.method == 'POST': if form.validate(): data_api_client.register_framework_agreement_returned( current_user.supplier_code, framework_slug, current_user.email_address, current_user.id ) email_recipients = [supplier_framework['declaration']['primaryContactEmail']] if supplier_framework['declaration']['primaryContactEmail'].lower() != current_user.email_address.lower(): email_recipients.append(current_user.email_address) try: email_body = render_template( 'emails/framework_agreement_with_framework_version_returned.html', framework_name=framework['name'], framework_slug=framework['slug'], framework_live_date=content_loader.get_message(framework_slug, 'dates')['framework_live_date'], # noqa ) send_email( email_recipients, email_body, 'Your {} signature page has been received'.format(framework['name']), current_app.config["DM_GENERIC_NOREPLY_EMAIL"], current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"], ['{}-framework-agreement'.format(framework_slug)], ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "Framework agreement email failed to send. " "error {error} supplier_code {supplier_code} email_hash {email_hash}", extra={'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address)}) abort(503, "Framework agreement email failed to send") session.pop('signature_page', None) flash( 'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.', 'success' ) return redirect(url_for(".framework_dashboard", framework_slug=framework_slug)) else: form_errors = [ {'question': form['authorisation'].label.text, 'input_name': 'authorisation'} ] form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format( supplier_framework['declaration']['nameOfOrganisation'] ) status_code = 400 if form_errors else 200 return render_template_with_csrf( "frameworks/contract_review.html", status_code=status_code, form=form, form_errors=form_errors, framework=framework, signature_page=signature_page, supplier_framework=supplier_framework, )
def framework_dashboard(framework_slug): framework = get_framework(data_api_client, framework_slug) if request.method == 'POST': register_interest_in_framework(data_api_client, framework_slug) supplier_users = data_api_client.find_users(supplier_code=current_user.supplier_code) try: email_body = render_template('emails/{}_application_started.html'.format(framework_slug)) send_email( [user['emailAddress'] for user in supplier_users['users'] if user['active']], email_body, 'You have started your {} application'.format(framework['name']), current_app.config['CLARIFICATION_EMAIL_FROM'], current_app.config['CLARIFICATION_EMAIL_NAME'], ['{}-application-started'.format(framework_slug)] ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "Application started email failed to send: {error}, supplier_code: {supplier_code}", extra={'error': six.text_type(e), 'supplier_code': current_user.supplier_code} ) drafts, complete_drafts = get_drafts(data_api_client, framework_slug) supplier_framework_info = get_supplier_framework_info(data_api_client, framework_slug) declaration_status = get_declaration_status_from_info(supplier_framework_info) supplier_is_on_framework = get_supplier_on_framework_from_info(supplier_framework_info) # Do not show a framework dashboard for earlier G-Cloud iterations if declaration_status == 'unstarted' and framework['status'] == 'live': abort(404) key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list(framework_slug, load_timestamps=True) key_list.reverse() first_page = content_loader.get_manifest( framework_slug, 'declaration' ).get_next_editable_section_id() # filenames supplier_pack_filename = '{}-supplier-pack.zip'.format(framework_slug) result_letter_filename = RESULT_LETTER_FILENAME countersigned_agreement_file = None if countersigned_framework_agreement_exists_in_bucket(framework_slug, current_app.config['DM_AGREEMENTS_BUCKET']): countersigned_agreement_file = COUNTERSIGNED_AGREEMENT_FILENAME application_made = supplier_is_on_framework or (len(complete_drafts) > 0 and declaration_status == 'complete') lots_with_completed_drafts = [lot for lot in framework['lots'] if count_drafts_by_lot(complete_drafts, lot['slug'])] last_modified = { 'supplier_pack': get_last_modified_from_first_matching_file( key_list, framework_slug, "communications/{}".format(supplier_pack_filename) ), 'supplier_updates': get_last_modified_from_first_matching_file( key_list, framework_slug, "communications/updates/" ) } # if supplier has returned agreement for framework with framework_agreement_version, show contract_submitted page if supplier_is_on_framework and framework['frameworkAgreementVersion'] and supplier_framework_info['agreementReturned']: # noqa agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) signature_page = get_most_recently_uploaded_agreement_file_or_none(agreements_bucket, framework_slug) return render_template( "frameworks/contract_submitted.html", framework=framework, framework_live_date=content_loader.get_message(framework_slug, 'dates')['framework_live_date'], document_name='{}.{}'.format(SIGNED_AGREEMENT_PREFIX, signature_page['ext']), supplier_framework=supplier_framework_info, supplier_pack_filename=supplier_pack_filename, last_modified=last_modified ), 200 return render_template( "frameworks/dashboard.html", application_made=application_made, completed_lots=tuple( dict(lot, complete_count=count_drafts_by_lot(complete_drafts, lot['slug'])) for lot in lots_with_completed_drafts ), counts={ "draft": len(drafts), "complete": len(complete_drafts) }, dates=content_loader.get_message(framework_slug, 'dates'), declaration_status=declaration_status, first_page_of_declaration=first_page, framework=framework, last_modified=last_modified, supplier_is_on_framework=supplier_is_on_framework, supplier_pack_filename=supplier_pack_filename, result_letter_filename=result_letter_filename, countersigned_agreement_file=countersigned_agreement_file ), 200
def view_contract_variation(framework_slug, variation_slug): framework = get_framework(data_api_client, framework_slug, allowed_statuses=['live']) supplier_framework = return_supplier_framework_info_if_on_framework_or_abort( data_api_client, framework_slug) variation_details = framework.get('variations', {}).get(variation_slug) # 404 if framework doesn't have contract variation if not variation_details: abort(404) # 404 if agreement hasn't been returned yet if not supplier_framework['agreementReturned']: abort(404) agreed_details = supplier_framework['agreedVariations'].get( variation_slug, {}) variation_content_name = 'contract_variation_{}'.format(variation_slug) content_loader.load_messages(framework_slug, [variation_content_name]) form = AcceptAgreementVariationForm() form_errors = None # Do not call API or send email if already agreed to if request.method == 'POST' and not agreed_details.get("agreedAt"): if form.validate_on_submit(): # Set variation as agreed to in database data_api_client.agree_framework_variation( current_user.supplier_id, framework_slug, variation_slug, current_user.id, current_user.email_address) # Send email confirming accepted try: email_body = render_template( 'emails/{}_variation_{}_agreed.html'.format( framework_slug, variation_slug)) send_email( returned_agreement_email_recipients(supplier_framework), email_body, current_app.config['DM_MANDRILL_API_KEY'], '{}: you have accepted the proposed contract variation'. format(framework['name']), current_app.config['CLARIFICATION_EMAIL_FROM'], current_app.config['CLARIFICATION_EMAIL_NAME'], ['{}-variation-accepted'.format(framework_slug)]) except MandrillException as e: current_app.logger.error( "Variation agreed email failed to send: {error}, supplier_id: {supplier_id}", extra={ 'error': six.text_type(e), 'supplier_id': current_user.supplier_id }) flash('variation_accepted') return redirect( url_for(".view_contract_variation", framework_slug=framework_slug, variation_slug=variation_slug)) else: form_errors = [{ 'question': form['accept_changes'].label.text, 'input_name': 'accept_changes' }] supplier_name = supplier_framework['declaration']['nameOfOrganisation'] variation_content = content_loader.get_message( framework_slug, variation_content_name).filter({'supplier_name': supplier_name}) return render_template( "frameworks/contract_variation.html", form=form, form_errors=form_errors, framework=framework, supplier_framework=supplier_framework, variation_details=variation_details, variation=variation_content, agreed_details=agreed_details, supplier_name=supplier_name, ), 400 if form_errors else 200
def upload_framework_agreement(framework_slug): """ This is the route used to upload agreements for pre-G-Cloud 8 frameworks """ framework = get_framework(data_api_client, framework_slug, allowed_statuses=['standstill', 'live']) # if there's a frameworkAgreementVersion key it means we're on G-Cloud 8 or higher and shouldn't be using this route if framework.get('frameworkAgreementVersion'): abort(404) supplier_framework = return_supplier_framework_info_if_on_framework_or_abort( data_api_client, framework_slug) upload_error = None if not file_is_less_than_5mb(request.files['agreement']): upload_error = "Document must be less than 5MB" elif file_is_empty(request.files['agreement']): upload_error = "Document must not be empty" if upload_error is not None: return render_template( "frameworks/agreement.html", framework=framework, supplier_framework=supplier_framework, upload_error=upload_error, agreement_filename=AGREEMENT_FILENAME, ), 400 agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) extension = get_extension(request.files['agreement'].filename) path = generate_timestamped_document_upload_path( framework_slug, current_user.supplier_id, 'agreements', '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension)) agreements_bucket.save( path, request.files['agreement'], acl='private', download_filename='{}-{}-{}{}'.format( sanitise_supplier_name(current_user.supplier_name), current_user.supplier_id, SIGNED_AGREEMENT_PREFIX, extension)) agreement_id = data_api_client.create_framework_agreement( current_user.supplier_id, framework_slug, current_user.email_address)['agreement']['id'] data_api_client.update_framework_agreement(agreement_id, {"signedAgreementPath": path}, current_user.email_address) data_api_client.sign_framework_agreement( agreement_id, current_user.email_address, {"uploaderUserId": current_user.id}) try: email_body = render_template( 'emails/framework_agreement_uploaded.html', framework_name=framework['name'], supplier_name=current_user.supplier_name, supplier_id=current_user.supplier_id, user_name=current_user.name) send_email( current_app.config['DM_FRAMEWORK_AGREEMENTS_EMAIL'], email_body, current_app.config['DM_MANDRILL_API_KEY'], '{} framework agreement'.format(framework['name']), current_app.config["DM_GENERIC_NOREPLY_EMAIL"], '{} Supplier'.format(framework['name']), ['{}-framework-agreement'.format(framework_slug)], reply_to=current_user.email_address, ) except MandrillException as e: current_app.logger.error( "Framework agreement email failed to send. " "error {error} supplier_id {supplier_id} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'email_hash': hash_email(current_user.email_address) }) abort(503, "Framework agreement email failed to send") return redirect( url_for('.framework_agreement', framework_slug=framework_slug))
def framework_dashboard(framework_slug): framework = get_framework(data_api_client, framework_slug) if request.method == 'POST': register_interest_in_framework(data_api_client, framework_slug) supplier_users = data_api_client.find_users( supplier_id=current_user.supplier_id) try: email_body = render_template( 'emails/{}_application_started.html'.format(framework_slug)) send_email([ user['emailAddress'] for user in supplier_users['users'] if user['active'] ], email_body, current_app.config['DM_MANDRILL_API_KEY'], 'You started a {} application'.format( framework['name']), current_app.config['CLARIFICATION_EMAIL_FROM'], current_app.config['CLARIFICATION_EMAIL_NAME'], ['{}-application-started'.format(framework_slug)]) except MandrillException as e: current_app.logger.error( "Application started email failed to send: {error}, supplier_id: {supplier_id}", extra={ 'error': six.text_type(e), 'supplier_id': current_user.supplier_id }) drafts, complete_drafts = get_drafts(data_api_client, framework_slug) supplier_framework_info = get_supplier_framework_info( data_api_client, framework_slug) declaration_status = get_declaration_status_from_info( supplier_framework_info) supplier_is_on_framework = get_supplier_on_framework_from_info( supplier_framework_info) # Do not show a framework dashboard for earlier G-Cloud iterations if declaration_status == 'unstarted' and framework['status'] == 'live': abort(404) application_made = supplier_is_on_framework or ( len(complete_drafts) > 0 and declaration_status == 'complete') lots_with_completed_drafts = [ lot for lot in framework['lots'] if count_drafts_by_lot(complete_drafts, lot['slug']) ] first_page = content_loader.get_manifest( framework_slug, 'declaration').get_next_editable_section_id() framework_dates = content_loader.get_message(framework_slug, 'dates') framework_urls = content_loader.get_message(framework_slug, 'urls') # filenames result_letter_filename = RESULT_LETTER_FILENAME countersigned_agreement_file = None if supplier_framework_info and supplier_framework_info['countersignedPath']: countersigned_agreement_file = degenerate_document_path_and_return_doc_name( supplier_framework_info['countersignedPath']) signed_agreement_document_name = None if supplier_is_on_framework and supplier_framework_info[ 'agreementReturned']: signed_agreement_document_name = degenerate_document_path_and_return_doc_name( supplier_framework_info['agreementPath']) key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list( framework_slug, load_timestamps=True) key_list.reverse() base_communications_files = { "invitation": { "path": "communications/", "filename": "{}-invitation.pdf".format(framework_slug), }, "proposed_agreement": { "path": "communications/", "filename": "{}-proposed-framework-agreement.pdf".format(framework_slug), }, "final_agreement": { "path": "communications/", "filename": "{}-final-framework-agreement.pdf".format(framework_slug), }, "proposed_call_off": { "path": "communications/", "filename": "{}-proposed-call-off.pdf".format(framework_slug), }, "final_call_off": { "path": "communications/", "filename": "{}-final-call-off.pdf".format(framework_slug), }, "reporting_template": { "path": "communications/", "filename": "{}-reporting-template.xls".format(framework_slug), }, "supplier_updates": { "path": "communications/updates/", }, } # now we annotate these with last_modified information which also tells us whether the file exists communications_files = { label: dict( d, last_modified=get_last_modified_from_first_matching_file( key_list, framework_slug, d["path"] + d.get("filename", ""), ), ) for label, d in six.iteritems(base_communications_files) } return render_template( "frameworks/dashboard.html", application_made=application_made, communications_files=communications_files, completed_lots=tuple( dict(lot, complete_count=count_drafts_by_lot(complete_drafts, lot['slug'])) for lot in lots_with_completed_drafts), countersigned_agreement_file=countersigned_agreement_file, counts={ "draft": len(drafts), "complete": len(complete_drafts) }, declaration_status=declaration_status, signed_agreement_document_name=signed_agreement_document_name, first_page_of_declaration=first_page, framework=framework, framework_dates=framework_dates, framework_urls=framework_urls, result_letter_filename=result_letter_filename, supplier_framework=supplier_framework_info, supplier_is_on_framework=supplier_is_on_framework, ), 200
def invite_user(supplier_id): invite_form = EmailAddressForm() try: suppliers = data_api_client.get_supplier(supplier_id) users = data_api_client.find_users(supplier_id) except HTTPError as e: current_app.logger.error(str(e), supplier_id) if e.status_code != 404: raise else: abort(404, "Supplier not found") if invite_form.validate_on_submit(): token = generate_token( { "supplier_id": supplier_id, "supplier_name": suppliers['suppliers']['name'], "email_address": invite_form.email_address.data }, current_app.config['SHARED_EMAIL_KEY'], current_app.config['INVITE_EMAIL_SALT'] ) url = "{}{}/{}".format( request.url_root, current_app.config['CREATE_USER_PATH'], format(token) ) email_body = render_template( "emails/invite_user_email.html", url=url, supplier=suppliers['suppliers']['name']) try: send_email( invite_form.email_address.data, email_body, current_app.config['DM_MANDRILL_API_KEY'], current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'], ["user-invite"] ) except MandrillException as e: current_app.logger.error( "Invitation email failed to send error {} to {} supplier {} supplier id {} ".format( str(e), invite_form.email_address.data, current_user.supplier_name, current_user.supplier_id) ) abort(503, "Failed to send user invite reset") data_api_client.create_audit_event( audit_type=AuditTypes.invite_user, user=current_user.email_address, object_type='suppliers', object_id=supplier_id, data={'invitedEmail': invite_form.email_address.data}) flash('user_invited', 'success') return redirect(url_for('.find_supplier_users', supplier_id=supplier_id)) else: return render_template( "view_supplier_users.html", invite_form=invite_form, move_user_form=MoveUserForm(), users=users["users"], supplier=suppliers["suppliers"] ), 400
def send_brief_clarification_question(data_api_client, brief, clarification_question): # Email the question to brief owners email_body = render_template( "emails/brief_clarification_question.html", brief_id=brief['id'], brief_name=brief['title'], publish_by_date=brief['clarificationQuestionsPublishedBy'], framework_slug=brief['frameworkSlug'], lot_slug=brief['lotSlug'], message=clarification_question, ) try: send_email( to_email_addresses=get_brief_user_emails(brief), email_body=email_body, api_key=current_app.config['DM_MANDRILL_API_KEY'], subject=u"You’ve received a new supplier question about ‘{}’". format(brief['title']), from_email=current_app.config['CLARIFICATION_EMAIL_FROM'], from_name="{} Supplier".format(brief['frameworkName']), tags=["brief-clarification-question"]) except MandrillException as e: current_app.logger.error( "Brief question email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}", extra={ 'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'brief_id': brief['id'] }) abort(503, "Clarification question email failed to send") data_api_client.create_audit_event( audit_type=AuditTypes.send_clarification_question, user=current_user.email_address, object_type="briefs", object_id=brief['id'], data={ "question": clarification_question, "briefId": brief['id'] }) # Send the supplier a copy of the question supplier_email_body = render_template( "emails/brief_clarification_question_confirmation.html", brief_id=brief['id'], brief_name=brief['title'], framework_slug=brief['frameworkSlug'], message=clarification_question) try: send_email(to_email_addresses=[current_user.email_address], email_body=supplier_email_body, api_key=current_app.config['DM_MANDRILL_API_KEY'], subject=u"Your question about ‘{}’".format(brief['title']), from_email=current_app.config['CLARIFICATION_EMAIL_FROM'], from_name=current_app.config['CLARIFICATION_EMAIL_NAME'], tags=["brief-clarification-question-confirmation"]) except MandrillException as e: current_app.logger.error( "Brief question supplier email failed to send. error={error} supplier_id={supplier_id} brief_id={brief_id}", extra={ 'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'brief_id': brief['id'] })
def submit_company_summary(): required_fields = [ "email_address", "phone_number", "contact_name", "duns_number", "company_name", "account_email_address" ] missing_fields = [ field for field in required_fields if field not in session ] if not missing_fields: supplier = { "name": session["company_name"], "dunsNumber": str(session["duns_number"]), "contactInformation": [{ "email": session["email_address"], "phoneNumber": session["phone_number"], "contactName": session["contact_name"] }] } if session.get("companies_house_number", None): supplier["companiesHouseNumber"] = session.get( "companies_house_number") account_email_address = session.get("account_email_address", None) supplier = data_api_client.create_supplier(supplier) session.clear() session['email_company_name'] = supplier['suppliers']['name'] session['email_supplier_id'] = supplier['suppliers']['id'] token = generate_token( { "email_address": account_email_address, "supplier_id": session['email_supplier_id'], "supplier_name": session['email_company_name'] }, current_app.config['SHARED_EMAIL_KEY'], current_app.config['INVITE_EMAIL_SALT']) url = url_for('main.create_user', encoded_token=token, _external=True) email_body = render_template( "emails/create_user_email.html", company_name=session['email_company_name'], url=url) try: send_email(account_email_address, email_body, current_app.config['DM_MANDRILL_API_KEY'], current_app.config['CREATE_USER_SUBJECT'], current_app.config['RESET_PASSWORD_EMAIL_FROM'], current_app.config['RESET_PASSWORD_EMAIL_NAME'], ["user-creation"]) session['email_sent_to'] = account_email_address except MandrillException as e: current_app.logger.error( "suppliercreate.fail: Create user email failed to send. " "error {error} supplier_id {supplier_id} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'supplier_id': session['email_supplier_id'], 'email_hash': hash_email(account_email_address) }) abort(503, "Failed to send user creation email") data_api_client.create_audit_event( audit_type=AuditTypes.invite_user, object_type='suppliers', object_id=session['email_supplier_id'], data={'invitedEmail': account_email_address}) return redirect(url_for('.create_your_account_complete'), 302) else: return render_template("suppliers/company_summary.html", missing_fields=missing_fields), 400
def framework_updates_email_clarification_question(framework_slug): framework = get_framework(data_api_client, framework_slug) # Stripped input should not empty clarification_question = request.form.get(CLARIFICATION_QUESTION_NAME, '').strip() if not clarification_question: return framework_updates(framework_slug, "Question cannot be empty") elif len(clarification_question) > 5000: return framework_updates( framework_slug, error_message="Question cannot be longer than 5000 characters", default_textbox_value=clarification_question) # Submit email to Zendesk so the question can be answered # Fail if this email does not send if framework['clarificationQuestionsOpen']: subject = "{} clarification question".format(framework['name']) to_address = current_app.config['DM_CLARIFICATION_QUESTION_EMAIL'] from_address = "suppliers+{}@digitalmarketplace.service.gov.uk".format( framework['slug']) email_body = render_template("emails/clarification_question.html", supplier_name=current_user.supplier_name, user_name=current_user.name, message=clarification_question) tags = ["clarification-question"] else: subject = "{} application question".format(framework['name']) to_address = current_app.config['DM_FOLLOW_UP_EMAIL_TO'] from_address = current_user.email_address email_body = render_template("emails/follow_up_question.html", supplier_name=current_user.supplier_name, user_name=current_user.name, framework_name=framework['name'], message=clarification_question) tags = ["application-question"] try: send_email( to_address, email_body, current_app.config['DM_MANDRILL_API_KEY'], subject, current_app.config["DM_GENERIC_NOREPLY_EMAIL"], "{} Supplier".format(framework['name']), tags, reply_to=from_address, ) except MandrillException as e: current_app.logger.error( "{framework} clarification question email failed to send. " "error {error} supplier_id {supplier_id} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'framework': framework['slug'], 'supplier_id': current_user.supplier_id, 'email_hash': hash_email(current_user.email_address) }) abort(503, "Clarification question email failed to send") if framework['clarificationQuestionsOpen']: # Send confirmation email to the user who submitted the question # No need to fail if this email does not send subject = current_app.config['CLARIFICATION_EMAIL_SUBJECT'] tags = ["clarification-question-confirm"] audit_type = AuditTypes.send_clarification_question email_body = render_template( "emails/clarification_question_submitted.html", user_name=current_user.name, framework_name=framework['name'], message=clarification_question) try: send_email(current_user.email_address, email_body, current_app.config['DM_MANDRILL_API_KEY'], subject, current_app.config['CLARIFICATION_EMAIL_FROM'], current_app.config['CLARIFICATION_EMAIL_NAME'], tags) except MandrillException as e: current_app.logger.error( "{framework} clarification question confirmation email failed to send. " "error {error} supplier_id {supplier_id} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'framework': framework['slug'], 'supplier_id': current_user.supplier_id, 'email_hash': hash_email(current_user.email_address) }) else: # Do not send confirmation email to the user who submitted the question # Zendesk will handle this instead audit_type = AuditTypes.send_application_question data_api_client.create_audit_event(audit_type=audit_type, user=current_user.email_address, object_type="suppliers", object_id=current_user.supplier_id, data={ "question": clarification_question, 'framework': framework['slug'] }) flash('message_sent', 'success') return framework_updates(framework['slug'])
def send_reset_password_email(): form = auth_forms.EmailAddressForm(request.form) if form.validate(): email_address = form.email_address.data user_json = data_api_client.get_user(email_address=email_address) if user_json is not None: user = User.from_json(user_json) token = generate_token( { "user": user.id, "email": user.email_address }, current_app.config['SECRET_KEY'], current_app.config['RESET_PASSWORD_SALT']) url = url_for('main.reset_password', token=token, _external=True) email_body = render_template("emails/reset_password_email.html", url=url, locked=user.locked) try: send_email( user.email_address, email_body, current_app.config['RESET_PASSWORD_EMAIL_SUBJECT'], current_app.config['RESET_PASSWORD_EMAIL_FROM'], current_app.config['RESET_PASSWORD_EMAIL_NAME'], ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "Password reset email failed to send. " "error {error} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'email_hash': hash_email(user.email_address) }) abort(503, response="Failed to send password reset.") current_app.logger.info( "login.reset-email.sent: Sending password reset email for " "supplier_code {supplier_code} email_hash {email_hash}", extra={ 'supplier_code': user.supplier_code, 'email_hash': hash_email(user.email_address) }) else: current_app.logger.info( "login.reset-email.invalid-email: " "Password reset request for invalid supplier email {email_hash}", extra={'email_hash': hash_email(email_address)}) flash('email_sent') return redirect(url_for('.request_password_reset')) else: return render_template_with_csrf("auth/request-password-reset.html", status_code=400, form=form)
def contract_review(framework_slug, agreement_id): framework = get_framework(data_api_client, framework_slug, allowed_statuses=['standstill', 'live']) # if there's no frameworkAgreementVersion key it means we're pre-G-Cloud 8 and shouldn't be using this route if not framework.get('frameworkAgreementVersion'): abort(404) supplier_framework = return_supplier_framework_info_if_on_framework_or_abort( data_api_client, framework_slug) agreement = data_api_client.get_framework_agreement( agreement_id)['agreement'] check_agreement_is_related_to_supplier_framework_or_abort( agreement, supplier_framework) # if framework agreement doesn't have a name or a role or the agreement file, then 404 if not (agreement.get('signedAgreementDetails') and agreement['signedAgreementDetails'].get('signerName') and agreement['signedAgreementDetails'].get('signerRole') and agreement.get('signedAgreementPath')): abort(404) agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) signature_page = agreements_bucket.get_key( agreement['signedAgreementPath']) form = ContractReviewForm() form_errors = None if request.method == 'POST': if form.validate_on_submit(): data_api_client.sign_framework_agreement( agreement_id, current_user.email_address, {'uploaderUserId': current_user.id}) try: email_body = render_template( 'emails/framework_agreement_with_framework_version_returned.html', framework_name=framework['name'], framework_slug=framework['slug'], framework_live_date=content_loader.get_message( framework_slug, 'dates')['framework_live_date'], # noqa ) send_email( returned_agreement_email_recipients(supplier_framework), email_body, current_app.config['DM_MANDRILL_API_KEY'], 'Your {} signature page has been received'.format( framework['name']), current_app.config["DM_GENERIC_NOREPLY_EMAIL"], current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"], ['{}-framework-agreement'.format(framework_slug)], ) except MandrillException as e: current_app.logger.error( "Framework agreement email failed to send. " "error {error} supplier_id {supplier_id} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'supplier_id': current_user.supplier_id, 'email_hash': hash_email(current_user.email_address) }) abort(503, "Framework agreement email failed to send") session.pop('signature_page', None) flash( 'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.', 'success') if feature.is_active('CONTRACT_VARIATION'): # Redirect to contract variation if it has not been signed if (framework.get('variations') and not supplier_framework['agreedVariations']): variation_slug = list(framework['variations'].keys())[0] return redirect( url_for('.view_contract_variation', framework_slug=framework_slug, variation_slug=variation_slug)) return redirect( url_for(".framework_dashboard", framework_slug=framework_slug)) else: form_errors = [{ 'question': form['authorisation'].label.text, 'input_name': 'authorisation' }] form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format( supplier_framework['declaration']['nameOfOrganisation']) return render_template( "frameworks/contract_review.html", agreement=agreement, form=form, form_errors=form_errors, framework=framework, signature_page=signature_page, supplier_framework=supplier_framework, ), 400 if form_errors else 200
def framework_dashboard(framework_slug): framework = get_framework(data_api_client, framework_slug) if request.method == 'POST': register_interest_in_framework(data_api_client, framework_slug) supplier_users = data_api_client.find_users( supplier_code=current_user.supplier_code) try: email_body = render_template( 'emails/{}_application_started.html'.format(framework_slug)) send_email([ user['emailAddress'] for user in supplier_users['users'] if user['active'] ], email_body, 'You have started your {} application'.format( framework['name']), current_app.config['CLARIFICATION_EMAIL_FROM'], current_app.config['CLARIFICATION_EMAIL_NAME'], ['{}-application-started'.format(framework_slug)]) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "Application started email failed to send: {error}, supplier_code: {supplier_code}", extra={ 'error': six.text_type(e), 'supplier_code': current_user.supplier_code }) drafts, complete_drafts = get_drafts(data_api_client, framework_slug) supplier_framework_info = get_supplier_framework_info( data_api_client, framework_slug) declaration_status = get_declaration_status_from_info( supplier_framework_info) supplier_is_on_framework = get_supplier_on_framework_from_info( supplier_framework_info) # Do not show a framework dashboard for earlier G-Cloud iterations if declaration_status == 'unstarted' and framework['status'] == 'live': abort(404) key_list = s3.S3(current_app.config['DM_COMMUNICATIONS_BUCKET']).list( framework_slug, load_timestamps=True) key_list.reverse() first_page = content_loader.get_manifest( framework_slug, 'declaration').get_next_editable_section_id() # filenames supplier_pack_filename = '{}-supplier-pack.zip'.format(framework_slug) result_letter_filename = RESULT_LETTER_FILENAME countersigned_agreement_file = None if countersigned_framework_agreement_exists_in_bucket( framework_slug, current_app.config['DM_AGREEMENTS_BUCKET']): countersigned_agreement_file = COUNTERSIGNED_AGREEMENT_FILENAME application_made = supplier_is_on_framework or ( len(complete_drafts) > 0 and declaration_status == 'complete') lots_with_completed_drafts = [ lot for lot in framework['lots'] if count_drafts_by_lot(complete_drafts, lot['slug']) ] last_modified = { 'supplier_pack': get_last_modified_from_first_matching_file( key_list, framework_slug, "communications/{}".format(supplier_pack_filename)), 'supplier_updates': get_last_modified_from_first_matching_file(key_list, framework_slug, "communications/updates/") } # if supplier has returned agreement for framework with framework_agreement_version, show contract_submitted page if supplier_is_on_framework and framework[ 'frameworkAgreementVersion'] and supplier_framework_info[ 'agreementReturned']: # noqa agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) signature_page = get_most_recently_uploaded_agreement_file_or_none( agreements_bucket, framework_slug) return render_template("frameworks/contract_submitted.html", framework=framework, framework_live_date=content_loader.get_message( framework_slug, 'dates')['framework_live_date'], document_name='{}.{}'.format( SIGNED_AGREEMENT_PREFIX, signature_page['ext']), supplier_framework=supplier_framework_info, supplier_pack_filename=supplier_pack_filename, last_modified=last_modified), 200 return render_template( "frameworks/dashboard.html", application_made=application_made, completed_lots=tuple( dict(lot, complete_count=count_drafts_by_lot(complete_drafts, lot['slug'])) for lot in lots_with_completed_drafts), counts={ "draft": len(drafts), "complete": len(complete_drafts) }, dates=content_loader.get_message(framework_slug, 'dates'), declaration_status=declaration_status, first_page_of_declaration=first_page, framework=framework, last_modified=last_modified, supplier_is_on_framework=supplier_is_on_framework, supplier_pack_filename=supplier_pack_filename, result_letter_filename=result_letter_filename, countersigned_agreement_file=countersigned_agreement_file), 200
def framework_updates_email_clarification_question(framework_slug): framework = get_framework(data_api_client, framework_slug) # Stripped input should not empty clarification_question = request.form.get(CLARIFICATION_QUESTION_NAME, '').strip() if not clarification_question: return framework_updates(framework_slug, "Question cannot be empty") elif len(clarification_question) > 5000: return framework_updates( framework_slug, error_message="Question cannot be longer than 5000 characters", default_textbox_value=clarification_question ) # Submit email to Zendesk so the question can be answered # Fail if this email does not send if framework['clarificationQuestionsOpen']: subject = "{} clarification question".format(framework['name']) to_address = current_app.config['DM_CLARIFICATION_QUESTION_EMAIL'] # FIXME: we have login_required, so should we use the supplier's email address instead? # Decide once we're actually using this feature. from_address = 'marketplace+{}@digital.gov.au'.format(framework['slug']) email_body = render_template( "emails/clarification_question.html", supplier_name=current_user.supplier_name, user_name=current_user.name, message=clarification_question ) tags = ["clarification-question"] else: subject = "{} application question".format(framework['name']) to_address = current_app.config['DM_FOLLOW_UP_EMAIL_TO'] from_address = current_user.email_address email_body = render_template( "emails/follow_up_question.html", supplier_name=current_user.supplier_name, user_name=current_user.name, framework_name=framework['name'], message=clarification_question ) tags = ["application-question"] try: send_email( to_address, email_body, subject, current_app.config["DM_GENERIC_NOREPLY_EMAIL"], "{} Supplier".format(framework['name']), tags, reply_to=from_address, ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "{framework} clarification question email failed to send. " "error {error} supplier_code {supplier_code} email_hash {email_hash}", extra={'error': six.text_type(e), 'framework': framework['slug'], 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address)}) abort(503, "Clarification question email failed to send") if framework['clarificationQuestionsOpen']: # Send confirmation email to the user who submitted the question # No need to fail if this email does not send subject = current_app.config['CLARIFICATION_EMAIL_SUBJECT'] tags = ["clarification-question-confirm"] audit_type = AuditTypes.send_clarification_question email_body = render_template( "emails/clarification_question_submitted.html", user_name=current_user.name, framework_name=framework['name'], message=clarification_question ) try: send_email( current_user.email_address, email_body, subject, current_app.config['CLARIFICATION_EMAIL_FROM'], current_app.config['CLARIFICATION_EMAIL_NAME'], tags ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "{framework} clarification question confirmation email failed to send. " "error {error} supplier_code {supplier_code} email_hash {email_hash}", extra={'error': six.text_type(e), 'framework': framework['slug'], 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address)}) else: # Do not send confirmation email to the user who submitted the question # Zendesk will handle this instead audit_type = AuditTypes.send_application_question data_api_client.create_audit_event( audit_type=audit_type, user=current_user.email_address, object_type="suppliers", object_id=current_user.supplier_code, data={"question": clarification_question, 'framework': framework['slug']}) flash('message_sent', 'success') return framework_updates(framework['slug'])
def upload_framework_agreement(framework_slug): framework = get_framework(data_api_client, framework_slug, allowed_statuses=['standstill', 'live']) supplier_framework = return_supplier_framework_info_if_on_framework_or_abort( data_api_client, framework_slug) upload_error = None if not file_is_less_than_5mb(request.files['agreement']): upload_error = "Document must be less than 5MB" elif file_is_empty(request.files['agreement']): upload_error = "Document must not be empty" if upload_error is not None: return render_template_with_csrf("frameworks/agreement.html", status_code=400, framework=framework, supplier_framework=supplier_framework, upload_error=upload_error, agreement_filename=AGREEMENT_FILENAME) agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) extension = get_extension(request.files['agreement'].filename) path = get_agreement_document_path( framework_slug, current_user.supplier_code, '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension)) agreements_bucket.save( path, request.files['agreement'], acl='private', download_filename='{}-{}-{}{}'.format( sanitise_supplier_name(current_user.supplier_name), current_user.supplier_code, SIGNED_AGREEMENT_PREFIX, extension)) data_api_client.register_framework_agreement_returned( current_user.supplier_code, framework_slug, current_user.email_address) try: email_body = render_template( 'emails/framework_agreement_uploaded.html', framework_name=framework['name'], supplier_name=current_user.supplier_name, supplier_code=current_user.supplier_code, user_name=current_user.name) send_email( current_app.config['DM_FRAMEWORK_AGREEMENTS_EMAIL'], email_body, '{} framework agreement'.format(framework['name']), current_app.config["DM_GENERIC_NOREPLY_EMAIL"], '{} Supplier'.format(framework['name']), ['{}-framework-agreement'.format(framework_slug)], reply_to=current_user.email_address, ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "Framework agreement email failed to send. " "error {error} supplier_code {supplier_code} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address) }) abort(503, "Framework agreement email failed to send") return redirect( url_for('.framework_agreement', framework_slug=framework_slug))
def upload_framework_agreement(framework_slug): framework = get_framework(data_api_client, framework_slug, allowed_statuses=['standstill', 'live']) supplier_framework = return_supplier_framework_info_if_on_framework_or_abort(data_api_client, framework_slug) upload_error = None if not file_is_less_than_5mb(request.files['agreement']): upload_error = "Document must be less than 5MB" elif file_is_empty(request.files['agreement']): upload_error = "Document must not be empty" if upload_error is not None: return render_template_with_csrf( "frameworks/agreement.html", status_code=400, framework=framework, supplier_framework=supplier_framework, upload_error=upload_error, agreement_filename=AGREEMENT_FILENAME ) agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) extension = get_extension(request.files['agreement'].filename) path = get_agreement_document_path( framework_slug, current_user.supplier_code, '{}{}'.format(SIGNED_AGREEMENT_PREFIX, extension) ) agreements_bucket.save( path, request.files['agreement'], acl='private', download_filename='{}-{}-{}{}'.format( sanitise_supplier_name(current_user.supplier_name), current_user.supplier_code, SIGNED_AGREEMENT_PREFIX, extension ) ) data_api_client.register_framework_agreement_returned( current_user.supplier_code, framework_slug, current_user.email_address) try: email_body = render_template( 'emails/framework_agreement_uploaded.html', framework_name=framework['name'], supplier_name=current_user.supplier_name, supplier_code=current_user.supplier_code, user_name=current_user.name ) send_email( current_app.config['DM_FRAMEWORK_AGREEMENTS_EMAIL'], email_body, '{} framework agreement'.format(framework['name']), current_app.config["DM_GENERIC_NOREPLY_EMAIL"], '{} Supplier'.format(framework['name']), ['{}-framework-agreement'.format(framework_slug)], reply_to=current_user.email_address, ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "Framework agreement email failed to send. " "error {error} supplier_code {supplier_code} email_hash {email_hash}", extra={'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address)}) abort(503, "Framework agreement email failed to send") return redirect(url_for('.framework_agreement', framework_slug=framework_slug))
def contract_review(framework_slug): framework = get_framework(data_api_client, framework_slug) supplier_framework = return_supplier_framework_info_if_on_framework_or_abort( data_api_client, framework_slug) agreements_bucket = s3.S3(current_app.config['DM_AGREEMENTS_BUCKET']) signature_page = get_most_recently_uploaded_agreement_file_or_none( agreements_bucket, framework_slug) # if supplier_framework doesn't have a name or a role or the agreement file, then 404 if not (supplier_framework['agreementDetails'] and supplier_framework['agreementDetails'].get('signerName') and supplier_framework['agreementDetails'].get('signerRole') and signature_page): abort(404) form = ContractReviewForm(request.form) form_errors = None if request.method == 'POST': if form.validate(): data_api_client.register_framework_agreement_returned( current_user.supplier_code, framework_slug, current_user.email_address, current_user.id) email_recipients = [ supplier_framework['declaration']['primaryContactEmail'] ] if supplier_framework['declaration']['primaryContactEmail'].lower( ) != current_user.email_address.lower(): email_recipients.append(current_user.email_address) try: email_body = render_template( 'emails/framework_agreement_with_framework_version_returned.html', framework_name=framework['name'], framework_slug=framework['slug'], framework_live_date=content_loader.get_message( framework_slug, 'dates')['framework_live_date'], # noqa ) send_email( email_recipients, email_body, 'Your {} signature page has been received'.format( framework['name']), current_app.config["DM_GENERIC_NOREPLY_EMAIL"], current_app.config["FRAMEWORK_AGREEMENT_RETURNED_NAME"], ['{}-framework-agreement'.format(framework_slug)], ) except EmailError as e: rollbar.report_exc_info() current_app.logger.error( "Framework agreement email failed to send. " "error {error} supplier_code {supplier_code} email_hash {email_hash}", extra={ 'error': six.text_type(e), 'supplier_code': current_user.supplier_code, 'email_hash': hash_email(current_user.email_address) }) abort(503, "Framework agreement email failed to send") session.pop('signature_page', None) flash( 'Your framework agreement has been returned to the Crown Commercial Service to be countersigned.', 'success') return redirect( url_for(".framework_dashboard", framework_slug=framework_slug)) else: form_errors = [{ 'question': form['authorisation'].label.text, 'input_name': 'authorisation' }] form.authorisation.description = u"I have the authority to return this agreement on behalf of {}.".format( supplier_framework['declaration']['nameOfOrganisation']) status_code = 400 if form_errors else 200 return render_template_with_csrf( "frameworks/contract_review.html", status_code=status_code, form=form, form_errors=form_errors, framework=framework, signature_page=signature_page, supplier_framework=supplier_framework, )
def invite_user(supplier_code): invite_form = InviteForm(request.form) try: supplier = data_api_client.get_supplier(supplier_code)['supplier'] users = data_api_client.find_users(supplier_code) except HTTPError as e: current_app.logger.error(str(e), supplier_code) if e.status_code != 404: raise else: abort(404, "Supplier not found") if invite_form.validate(): email_address = invite_form.email_address.data user_name = invite_form.name.data data = { 'name': user_name, 'emailAddress': email_address, 'supplierCode': supplier_code, 'supplierName': supplier['name'], } token = generate_token(data, current_app.config['SECRET_KEY'], current_app.config['INVITE_EMAIL_SALT']) url = '{}://{}/{}/{}'.format(current_app.config['DM_HTTP_PROTO'], current_app.config['DM_MAIN_SERVER_NAME'], current_app.config['CREATE_USER_PATH'], format(token)) email_body = render_template( 'emails/invite_user_email.html', url=url, supplier=supplier['name'], name=user_name, ) try: send_email( email_address, email_body, current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'], ) except EmailError as e: current_app.logger.error( 'Invitation email failed to send error {} to {} supplier {} supplier code {} ' .format(str(e), email_address, current_user.supplier_name, current_user.supplier_code)) abort(503, "Failed to send user invite reset") try: data_api_client.record_supplier_invite(supplier_code=supplier_code, email_address=email_address) except APIError as e: current_app.logger.warning( 'Could not record supplier invite for {}: {}'.format( e.message, email_address)) flash('user_invited', 'success') return redirect( url_for('.find_supplier_users', supplier_code=supplier_code)) else: return render_template_with_csrf("view_supplier_users.html", status_code=400, invite_form=invite_form, move_user_form=MoveUserForm(), users=users["users"], supplier=supplier)
def invite_user(supplier_code): invite_form = InviteForm(request.form) try: supplier = data_api_client.get_supplier(supplier_code)['supplier'] users = data_api_client.find_users(supplier_code) except HTTPError as e: current_app.logger.error(str(e), supplier_code) if e.status_code != 404: raise else: abort(404, "Supplier not found") if invite_form.validate(): email_address = invite_form.email_address.data user_name = invite_form.name.data data = { 'name': user_name, 'emailAddress': email_address, 'supplierCode': supplier_code, 'supplierName': supplier['name'], } token = generate_token(data, current_app.config['SECRET_KEY'], current_app.config['INVITE_EMAIL_SALT']) url = '{}://{}/{}/{}'.format( current_app.config['DM_HTTP_PROTO'], current_app.config['DM_MAIN_SERVER_NAME'], current_app.config['CREATE_USER_PATH'], format(token) ) email_body = render_template( 'emails/invite_user_email.html', url=url, supplier=supplier['name'], name=user_name, ) try: send_email( email_address, email_body, current_app.config['INVITE_EMAIL_SUBJECT'], current_app.config['INVITE_EMAIL_FROM'], current_app.config['INVITE_EMAIL_NAME'], ) except EmailError as e: current_app.logger.error( 'Invitation email failed to send error {} to {} supplier {} supplier code {} '.format( str(e), email_address, current_user.supplier_name, current_user.supplier_code) ) abort(503, "Failed to send user invite reset") try: data_api_client.record_supplier_invite( supplier_code=supplier_code, email_address=email_address ) except APIError as e: current_app.logger.warning('Could not record supplier invite for {}: {}'.format(e.message, email_address)) flash('user_invited', 'success') return redirect(url_for('.find_supplier_users', supplier_code=supplier_code)) else: return render_template_with_csrf( "view_supplier_users.html", status_code=400, invite_form=invite_form, move_user_form=MoveUserForm(), users=users["users"], supplier=supplier )