from datetime import datetime, timedelta from string import ascii_uppercase from flask import ( request, render_template, redirect, url_for, flash, abort, json, ) from flask_login import login_required, current_user from dateutil.parser import parse from notifications_utils.formatters import escape_html from notifications_utils.recipients import first_column_headings from notifications_python_client.errors import HTTPError from app.main import main from app.utils import user_has_permissions, get_template, get_help_argument from app.template_previews import TemplatePreview, get_page_count_for_letter from app.main.forms import ( ChooseTemplateType, SMSTemplateForm, EmailTemplateForm, LetterTemplateForm, SearchTemplatesForm, ) from app.main.views.send import get_example_csv_rows from app import service_api_client, current_service, template_statistics_client form_objects = { 'email': EmailTemplateForm, 'sms': SMSTemplateForm, 'letter': LetterTemplateForm } page_headings = { 'email': 'email', 'sms': 'text message' } @main.route("/services//templates/") @login_required @user_has_permissions( 'view_activity', 'send_texts', 'send_emails', 'manage_templates', 'manage_api_keys', admin_override=True, any_=True ) def view_template(service_id, template_id): template = service_api_client.get_service_template(service_id, str(template_id))['data'] return render_template( 'views/templates/template.html', template=get_template( template, current_service, expand_emails=True, letter_preview_url=url_for( '.view_letter_template_preview', service_id=service_id, template_id=template_id, filetype='png', ), show_recipient=True, page_count=get_page_count_for_letter(template), ), ) @main.route("/services//start-tour/") @login_required @user_has_permissions( 'view_activity', 'send_texts', 'send_emails', 'manage_templates', 'manage_api_keys', admin_override=True, any_=True ) def start_tour(service_id, template_id): template = service_api_client.get_service_template(service_id, str(template_id))['data'] if template['template_type'] != 'sms': abort(404) return render_template( 'views/templates/start-tour.html', template=get_template( template, current_service, show_recipient=True, ), help='1', ) @main.route("/services//templates") @main.route("/services//templates/") @login_required @user_has_permissions( 'view_activity', 'send_texts', 'send_emails', 'manage_templates', 'manage_api_keys', admin_override=True, any_=True, ) def choose_template(service_id, template_type='all'): templates = service_api_client.get_service_templates(service_id)['data'] has_multiple_template_types = len({ template['template_type'] for template in templates }) > 1 template_nav_items = [ (label, key, url_for('.choose_template', service_id=current_service['id'], template_type=key), '') for label, key in filter(None, [ ('All', 'all'), ('Text message', 'sms'), ('Email', 'email'), ('Letter', 'letter') if 'letter' in current_service['permissions'] else None, ]) ] templates_on_page = [ template for template in templates if template_type in ['all', template['template_type']] ] return render_template( 'views/templates/choose.html', templates=templates_on_page, show_search_box=(len(templates_on_page) > 7), show_template_nav=has_multiple_template_types and (len(templates) > 2), template_nav_items=template_nav_items, template_type=template_type, search_form=SearchTemplatesForm(), ) @main.route("/services//templates/.") @login_required @user_has_permissions('view_activity', admin_override=True) def view_letter_template_preview(service_id, template_id, filetype): if filetype not in ('pdf', 'png'): abort(404) db_template = service_api_client.get_service_template(service_id, template_id)['data'] return TemplatePreview.from_database_object(db_template, filetype, page=request.args.get('page')) def _view_template_version(service_id, template_id, version, letters_as_pdf=False): return dict(template=get_template( service_api_client.get_service_template(service_id, template_id, version=version)['data'], current_service, expand_emails=True, letter_preview_url=url_for( '.view_template_version_preview', service_id=service_id, template_id=template_id, version=version, filetype='png', ) if not letters_as_pdf else None )) @main.route("/services//templates//version/") @login_required @user_has_permissions( 'view_activity', 'send_texts', 'send_emails', 'manage_templates', 'manage_api_keys', admin_override=True, any_=True ) def view_template_version(service_id, template_id, version): return render_template( 'views/templates/template_history.html', **_view_template_version(service_id=service_id, template_id=template_id, version=version) ) @main.route("/services//templates//version/.") @login_required @user_has_permissions( 'view_activity', 'send_texts', 'send_emails', 'manage_templates', 'manage_api_keys', admin_override=True, any_=True ) def view_template_version_preview(service_id, template_id, version, filetype): db_template = service_api_client.get_service_template(service_id, template_id, version=version)['data'] return TemplatePreview.from_database_object(db_template, filetype) @main.route("/services//templates/add", methods=['GET', 'POST']) @login_required @user_has_permissions('manage_templates', admin_override=True) def add_template_by_type(service_id): form = ChooseTemplateType( include_letters='letter' in current_service['permissions'] ) if form.validate_on_submit(): if form.template_type.data == 'letter': blank_letter = service_api_client.create_service_template( 'Untitled', 'letter', 'Body', service_id, 'Main heading', 'normal', ) return redirect(url_for( '.view_template', service_id=service_id, template_id=blank_letter['data']['id'], )) return redirect(url_for( '.add_service_template', service_id=service_id, template_type=form.template_type.data, )) return render_template('views/templates/add.html', form=form) @main.route("/services//templates/add-", methods=['GET', 'POST']) @login_required @user_has_permissions('manage_templates', admin_override=True) def add_service_template(service_id, template_type): if template_type not in ['sms', 'email', 'letter']: abort(404) if 'letter' not in current_service['permissions'] and template_type == 'letter': abort(403) form = form_objects[template_type]() if form.validate_on_submit(): if form.process_type.data == 'priority': abort_403_if_not_admin_user() try: new_template = service_api_client.create_service_template( form.name.data, template_type, form.template_content.data, service_id, form.subject.data if hasattr(form, 'subject') else None, form.process_type.data ) except HTTPError as e: if ( e.status_code == 400 and 'content' in e.message and any(['character count greater than' in x for x in e.message['content']]) ): form.template_content.errors.extend(e.message['content']) else: raise e else: return redirect( url_for('.view_template', service_id=service_id, template_id=new_template['data']['id']) ) return render_template( 'views/edit-{}-template.html'.format(template_type), form=form, template_type=template_type, heading_action='Add' ) def abort_403_if_not_admin_user(): if not current_user.has_permissions([], admin_override=True): abort(403) @main.route("/services//templates//edit", methods=['GET', 'POST']) @login_required @user_has_permissions('manage_templates', admin_override=True) def edit_service_template(service_id, template_id): template = service_api_client.get_service_template(service_id, template_id)['data'] template['template_content'] = template['content'] form = form_objects[template['template_type']](**template) if form.validate_on_submit(): if form.process_type.data != template['process_type']: abort_403_if_not_admin_user() subject = form.subject.data if hasattr(form, 'subject') else None new_template = get_template({ 'name': form.name.data, 'content': form.template_content.data, 'subject': subject, 'template_type': template['template_type'], 'id': template['id'], 'process_type': form.process_type.data }, current_service) template_change = get_template(template, current_service).compare_to(new_template) if template_change.placeholders_added and not request.form.get('confirm'): example_column_headings = ( first_column_headings[new_template.template_type] + list(new_template.placeholders) ) return render_template( 'views/templates/breaking-change.html', template_change=template_change, new_template=new_template, column_headings=list(ascii_uppercase[:len(example_column_headings)]), example_rows=[ example_column_headings, get_example_csv_rows(new_template), get_example_csv_rows(new_template) ], form=form ) try: service_api_client.update_service_template( template_id, form.name.data, template['template_type'], form.template_content.data, service_id, subject, form.process_type.data ) except HTTPError as e: if e.status_code == 400: if 'content' in e.message and any(['character count greater than' in x for x in e.message['content']]): form.template_content.errors.extend(e.message['content']) else: raise e else: raise e else: return redirect(url_for( '.view_template', service_id=service_id, template_id=template_id )) return render_template( 'views/edit-{}-template.html'.format(template['template_type']), form=form, template_id=template_id, template_type=template['template_type'], heading_action='Edit' ) @main.route("/services//templates//delete", methods=['GET', 'POST']) @login_required @user_has_permissions('manage_templates', admin_override=True) def delete_service_template(service_id, template_id): template = service_api_client.get_service_template(service_id, template_id)['data'] if request.method == 'POST': service_api_client.delete_service_template(service_id, template_id) return redirect(url_for( '.choose_template', service_id=service_id, )) try: last_used_notification = template_statistics_client.get_template_statistics_for_template( service_id, template['id'] ) message = '{} was last used {} ago'.format( last_used_notification['template']['name'], get_human_readable_delta( parse(last_used_notification['created_at']).replace(tzinfo=None), datetime.utcnow()) ) except HTTPError as e: if e.status_code == 404: message = '{} has never been used'.format(template['name']) else: raise e flash('{}. Are you sure you want to delete it?'.format(message), 'delete') return render_template( 'views/templates/template.html', template=get_template( template, current_service, expand_emails=True, letter_preview_url=url_for( '.view_letter_template_preview', service_id=service_id, template_id=template['id'], filetype='png', ), show_recipient=True, ) ) @main.route("/services//templates//redact", methods=['GET']) @login_required @user_has_permissions('manage_templates', admin_override=True) def confirm_redact_template(service_id, template_id): return render_template( 'views/templates/template.html', template=get_template( service_api_client.get_service_template(service_id, template_id)['data'], current_service, expand_emails=True, letter_preview_url=url_for( '.view_letter_template_preview', service_id=service_id, template_id=template_id, filetype='png', ), show_recipient=True, ), show_redaction_message=True, ) @main.route("/services//templates//redact", methods=['POST']) @login_required @user_has_permissions('manage_templates', admin_override=True) def redact_template(service_id, template_id): service_api_client.redact_service_template(service_id, template_id) flash( 'Personalised content will be hidden for messages sent with this template', 'default_with_tick' ) return redirect(url_for( '.view_template', service_id=service_id, template_id=template_id, )) @main.route('/services//templates//versions') @login_required @user_has_permissions( 'view_activity', 'send_texts', 'send_emails', 'manage_templates', 'manage_api_keys', admin_override=True, any_=True ) def view_template_versions(service_id, template_id): return render_template( 'views/templates/choose_history.html', versions=[ get_template( template, current_service, expand_emails=True, letter_preview_url=url_for( '.view_template_version_preview', service_id=service_id, template_id=template_id, version=template['version'], filetype='png', ) ) for template in service_api_client.get_service_template_versions(service_id, template_id)['data'] ] ) def get_last_use_message(template_name, template_statistics): try: most_recent_use = max( parse(template_stats['updated_at']).replace(tzinfo=None) for template_stats in template_statistics ) except ValueError: return '{} has never been used'.format(template_name) return '{} was last used {} ago'.format( template_name, get_human_readable_delta(most_recent_use, datetime.utcnow()) ) def get_human_readable_delta(from_time, until_time): delta = until_time - from_time if delta < timedelta(seconds=60): return 'under a minute' elif delta < timedelta(hours=1): minutes = int(delta.seconds / 60) return '{} minute{}'.format(minutes, '' if minutes == 1 else 's') elif delta < timedelta(days=1): hours = int(delta.seconds / 3600) return '{} hour{}'.format(hours, '' if hours == 1 else 's') else: days = delta.days return '{} day{}'.format(days, '' if days == 1 else 's') def should_show_template(template_type): return ( template_type != 'letter' or 'letter' in current_service['permissions'] )