from flask import Blueprint, jsonify, request from notifications_utils import SMS_CHAR_COUNT_LIMIT from notifications_utils.template import SMSMessageTemplate from sqlalchemy.orm.exc import NoResultFound from app.dao.services_dao import dao_fetch_service_by_id from app.dao.template_folder_dao import dao_get_template_folder_by_id_and_service_id from app.dao.templates_dao import ( dao_create_template, dao_get_all_templates_for_service, dao_get_template_by_id_and_service_id, dao_get_template_versions, dao_redact_template, dao_update_template, ) from app.errors import InvalidRequest, register_errors from app.models import Template, TemplateType from app.notifications.validators import check_reply_to, service_has_permission from app.schema_validation import validate from app.schemas import ( template_history_schema, template_schema, template_schema_no_detail, ) from app.template.template_schemas import ( post_create_template_schema, post_update_template_schema, ) from app.utils import get_public_notify_type_text template_blueprint = Blueprint( "template", __name__, url_prefix="/service//template" ) register_errors(template_blueprint) def _content_count_greater_than_limit(content, template_type): if template_type == TemplateType.SMS: template = SMSMessageTemplate( {"content": content, "template_type": template_type} ) return template.is_message_too_long() return False def validate_parent_folder(template_json): if template_json.get("parent_folder_id"): try: return dao_get_template_folder_by_id_and_service_id( template_folder_id=template_json.pop("parent_folder_id"), service_id=template_json["service"], ) except NoResultFound: raise InvalidRequest("parent_folder_id not found", status_code=400) else: return None @template_blueprint.route("", methods=["POST"]) def create_template(service_id): fetched_service = dao_fetch_service_by_id(service_id=service_id) # permissions needs to be placed here otherwise marshmallow will interfere with versioning permissions = [p.permission for p in fetched_service.permissions] template_json = validate(request.get_json(), post_create_template_schema) folder = validate_parent_folder(template_json=template_json) new_template = Template.from_json(template_json, folder) if not service_has_permission(new_template.template_type, permissions): message = "Creating {} templates is not allowed".format( get_public_notify_type_text(new_template.template_type) ) errors = {"template_type": [message]} raise InvalidRequest(errors, 403) new_template.service = fetched_service over_limit = _content_count_greater_than_limit( new_template.content, new_template.template_type ) if over_limit: message = "Content has a character count greater than the limit of {}".format( SMS_CHAR_COUNT_LIMIT ) errors = {"content": [message]} raise InvalidRequest(errors, status_code=400) check_reply_to(service_id, new_template.reply_to, new_template.template_type) dao_create_template(new_template) return jsonify(data=template_schema.dump(new_template)), 201 @template_blueprint.route("/", methods=["POST"]) def update_template(service_id, template_id): fetched_template = dao_get_template_by_id_and_service_id( template_id=template_id, service_id=service_id ) if not service_has_permission( fetched_template.template_type, [p.permission for p in fetched_template.service.permissions], ): message = "Updating {} templates is not allowed".format( get_public_notify_type_text(fetched_template.template_type) ) errors = {"template_type": [message]} raise InvalidRequest(errors, 403) data = request.get_json() validate(data, post_update_template_schema) # if redacting, don't update anything else if data.get("redact_personalisation") is True: return redact_template(fetched_template, data) current_data = dict(template_schema.dump(fetched_template).items()) updated_template = dict(template_schema.dump(fetched_template).items()) updated_template.update(data) # Check if there is a change to make. if _template_has_not_changed(current_data, updated_template): return jsonify(data=updated_template), 200 over_limit = _content_count_greater_than_limit( updated_template["content"], fetched_template.template_type ) if over_limit: message = "Content has a character count greater than the limit of {}".format( SMS_CHAR_COUNT_LIMIT ) errors = {"content": [message]} raise InvalidRequest(errors, status_code=400) update_dict = template_schema.load(updated_template) if update_dict.archived: update_dict.folder = None dao_update_template(update_dict) return jsonify(data=template_schema.dump(update_dict)), 200 @template_blueprint.route("", methods=["GET"]) def get_all_templates_for_service(service_id): templates = dao_get_all_templates_for_service(service_id=service_id) if str(request.args.get("detailed", True)) == "True": data = template_schema.dump(templates, many=True) else: data = template_schema_no_detail.dump(templates, many=True) return jsonify(data=data) @template_blueprint.route("/", methods=["GET"]) def get_template_by_id_and_service_id(service_id, template_id): fetched_template = dao_get_template_by_id_and_service_id( template_id=template_id, service_id=service_id ) data = template_schema.dump(fetched_template) return jsonify(data=data) @template_blueprint.route("//preview", methods=["GET"]) def preview_template_by_id_and_service_id(service_id, template_id): fetched_template = dao_get_template_by_id_and_service_id( template_id=template_id, service_id=service_id ) data = template_schema.dump(fetched_template) template_object = fetched_template._as_utils_template_with_personalisation( request.args.to_dict() ) if template_object.missing_data: raise InvalidRequest( { "template": [ "Missing personalisation: {}".format( ", ".join(template_object.missing_data) ) ] }, status_code=400, ) data["subject"] = template_object.subject data["content"] = template_object.content_with_placeholders_filled_in return jsonify(data) @template_blueprint.route("//version/") def get_template_version(service_id, template_id, version): data = template_history_schema.dump( dao_get_template_by_id_and_service_id( template_id=template_id, service_id=service_id, version=version ) ) return jsonify(data=data) @template_blueprint.route("//versions") def get_template_versions(service_id, template_id): data = template_history_schema.dump( dao_get_template_versions(service_id=service_id, template_id=template_id), many=True, ) return jsonify(data=data) def _template_has_not_changed(current_data, updated_template): return all( current_data[key] == updated_template[key] for key in ("name", "content", "subject", "archived", "process_type") ) def redact_template(template, data): # we also don't need to check what was passed in redact_personalisation - its presence in the dict is enough. if "created_by" not in data: message = "Field is required" errors = {"created_by": [message]} raise InvalidRequest(errors, status_code=400) # if it's already redacted, then just return 200 straight away. if not template.redact_personalisation: dao_redact_template(template, data["created_by"]) return "null", 200