mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-25 00:33:41 -04:00
Serialise template immediately after fetching
This commit changes the code in post notification endpoint to handle a serialised template (ie a `dict`) rather than a database object. This is the first step towards being able to cache the template and not hit the database on every request. There should be no functional changes here, it’s just refactoring. There are some changes to the tests where the signature of functions has changed. Importing of the template schema has to be done at a function level, otherwise Marshmallow gets weird. This commit also copies the `JSONModel` class from the admin app, which turns serialised data (a dict made from JSON) into an object on which certain predefined properties are allowed. This means we can still do the caching of serialised data, without having to change too much of the code in the app, or make it ugly by sprinkling dict lookups everywhere. We’re not copying all of JSONModel from the admin app, just the bits we need. We don’t need to compare or hash these objects, they’re just used for lookups. And redefining `__getattribute__` scares Leo.
This commit is contained in:
48
app/json_models.py
Normal file
48
app/json_models.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from app.dao import templates_dao
|
||||
|
||||
|
||||
class JSONModel(ABC):
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def ALLOWED_PROPERTIES(self):
|
||||
pass
|
||||
|
||||
def __init__(self, _dict):
|
||||
self._dict = _dict
|
||||
for property in self.ALLOWED_PROPERTIES:
|
||||
setattr(self, property, _dict[property])
|
||||
|
||||
def __dir__(self):
|
||||
return super().__dir__() + list(sorted(self.ALLOWED_PROPERTIES))
|
||||
|
||||
|
||||
class TemplateJSONModel(JSONModel):
|
||||
ALLOWED_PROPERTIES = {
|
||||
'archived',
|
||||
'id',
|
||||
'postage',
|
||||
'process_type',
|
||||
'reply_to_text',
|
||||
'template_type',
|
||||
'version',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_id_and_service_id(cls, template_id, service_id):
|
||||
return cls(cls.get_dict(template_id, service_id))
|
||||
|
||||
@staticmethod
|
||||
def get_dict(template_id, service_id):
|
||||
from app.schemas import template_schema
|
||||
|
||||
fetched_template = templates_dao.dao_get_template_by_id_and_service_id(
|
||||
template_id=template_id,
|
||||
service_id=service_id
|
||||
)
|
||||
|
||||
template_dict = template_schema.dump(fetched_template).data
|
||||
|
||||
return template_dict
|
||||
@@ -6,7 +6,14 @@ from app.notifications.process_notifications import persist_notification
|
||||
|
||||
|
||||
def create_letter_notification(
|
||||
letter_data, template, api_key, status, reply_to_text=None, billable_units=None, updated_at=None
|
||||
letter_data,
|
||||
template,
|
||||
service,
|
||||
api_key,
|
||||
status,
|
||||
reply_to_text=None,
|
||||
billable_units=None,
|
||||
updated_at=None,
|
||||
):
|
||||
notification = persist_notification(
|
||||
template_id=template.id,
|
||||
@@ -14,7 +21,7 @@ def create_letter_notification(
|
||||
template_postage=template.postage,
|
||||
# we only accept addresses_with_underscores from the API (from CSV we also accept dashes, spaces etc)
|
||||
recipient=PostalAddress.from_personalisation(letter_data['personalisation']).normalised,
|
||||
service=template.service,
|
||||
service=service,
|
||||
personalisation=letter_data['personalisation'],
|
||||
notification_type=LETTER_TYPE,
|
||||
api_key_id=api_key.id,
|
||||
|
||||
@@ -9,6 +9,11 @@ from notifications_utils.recipients import (
|
||||
validate_and_format_phone_number,
|
||||
format_email_address
|
||||
)
|
||||
from notifications_utils.template import (
|
||||
PlainTextEmailTemplate,
|
||||
SMSMessageTemplate,
|
||||
LetterPrintTemplate,
|
||||
)
|
||||
from notifications_utils.timezones import convert_bst_to_utc
|
||||
|
||||
from app import redis_store
|
||||
@@ -44,7 +49,17 @@ REDIS_GET_AND_INCR_DAILY_LIMIT_DURATION_SECONDS = Histogram(
|
||||
|
||||
|
||||
def create_content_for_notification(template, personalisation):
|
||||
template_object = template._as_utils_template_with_personalisation(personalisation)
|
||||
if template.template_type == EMAIL_TYPE:
|
||||
template_object = PlainTextEmailTemplate(template._dict, personalisation)
|
||||
if template.template_type == SMS_TYPE:
|
||||
template_object = SMSMessageTemplate(template._dict, personalisation)
|
||||
if template.template_type == LETTER_TYPE:
|
||||
template_object = LetterPrintTemplate(
|
||||
template._dict,
|
||||
personalisation,
|
||||
contact_block=template.reply_to_text,
|
||||
)
|
||||
|
||||
check_placeholders(template_object)
|
||||
|
||||
return template_object
|
||||
|
||||
@@ -128,7 +128,7 @@ def send_notification(notification_type):
|
||||
api_key_id=api_user.id,
|
||||
key_type=api_user.key_type,
|
||||
simulated=simulated,
|
||||
reply_to_text=template.get_reply_to_text()
|
||||
reply_to_text=template.reply_to_text
|
||||
)
|
||||
if not simulated:
|
||||
queue_name = QueueNames.PRIORITY if template.process_type == PRIORITY else None
|
||||
|
||||
@@ -8,7 +8,7 @@ from notifications_utils.recipients import (
|
||||
)
|
||||
from notifications_utils.clients.redis import rate_limit_cache_key, daily_limit_cache_key
|
||||
|
||||
from app.dao import services_dao, templates_dao
|
||||
from app.dao import services_dao
|
||||
from app.dao.service_sms_sender_dao import dao_get_service_sms_senders_by_id
|
||||
from app.models import (
|
||||
INTERNATIONAL_SMS_TYPE, SMS_TYPE, EMAIL_TYPE, LETTER_TYPE,
|
||||
@@ -21,6 +21,7 @@ from app.notifications.process_notifications import create_content_for_notificat
|
||||
from app.utils import get_public_notify_type_text
|
||||
from app.dao.service_email_reply_to_dao import dao_get_reply_to_by_id
|
||||
from app.dao.service_letter_contact_dao import dao_get_letter_contact_by_id
|
||||
from app.json_models import TemplateJSONModel
|
||||
|
||||
from gds_metrics.metrics import Histogram
|
||||
|
||||
@@ -156,11 +157,9 @@ def check_notification_content_is_not_empty(template_with_content):
|
||||
|
||||
|
||||
def validate_template(template_id, personalisation, service, notification_type):
|
||||
|
||||
try:
|
||||
template = templates_dao.dao_get_template_by_id_and_service_id(
|
||||
template_id=template_id,
|
||||
service_id=service.id
|
||||
)
|
||||
template = TemplateJSONModel.from_id_and_service_id(template_id, service.id)
|
||||
except NoResultFound:
|
||||
message = 'Template not found'
|
||||
raise BadRequestError(message=message,
|
||||
|
||||
@@ -98,14 +98,13 @@ def post_precompiled_letter_notification():
|
||||
'address_line_1': 'Provided as PDF'
|
||||
}
|
||||
|
||||
reply_to = get_reply_to_text(LETTER_TYPE, form, template)
|
||||
|
||||
notification = process_letter_notification(
|
||||
letter_data=form,
|
||||
api_key=api_user,
|
||||
service=authenticated_service,
|
||||
template=template,
|
||||
template_with_content=None, # not required for precompiled
|
||||
reply_to_text=reply_to,
|
||||
reply_to_text='', # not required for precompiled
|
||||
precompiled=True
|
||||
)
|
||||
|
||||
@@ -147,6 +146,7 @@ def post_notification(notification_type):
|
||||
notification = process_letter_notification(
|
||||
letter_data=form,
|
||||
api_key=api_user,
|
||||
service=authenticated_service,
|
||||
template=template,
|
||||
template_with_content=template_with_content,
|
||||
reply_to_text=reply_to
|
||||
@@ -156,7 +156,8 @@ def post_notification(notification_type):
|
||||
form=form,
|
||||
notification_type=notification_type,
|
||||
api_key=api_user,
|
||||
template=template_with_content,
|
||||
template=template,
|
||||
template_with_content=template_with_content,
|
||||
template_process_type=template.process_type,
|
||||
service=authenticated_service,
|
||||
reply_to_text=reply_to
|
||||
@@ -166,7 +167,15 @@ def post_notification(notification_type):
|
||||
|
||||
|
||||
def process_sms_or_email_notification(
|
||||
*, form, notification_type, api_key, template, template_process_type, service, reply_to_text=None
|
||||
*,
|
||||
form,
|
||||
notification_type,
|
||||
api_key,
|
||||
template,
|
||||
template_with_content,
|
||||
template_process_type,
|
||||
service,
|
||||
reply_to_text=None,
|
||||
):
|
||||
notification_id = uuid.uuid4()
|
||||
form_send_to = form['email_address'] if notification_type == EMAIL_TYPE else form['phone_number']
|
||||
@@ -186,19 +195,19 @@ def process_sms_or_email_notification(
|
||||
)
|
||||
if document_download_count:
|
||||
# We changed personalisation which means we need to update the content
|
||||
template.values = personalisation
|
||||
template_with_content.values = personalisation
|
||||
key_type = api_key.key_type
|
||||
service_in_research_mode = service.research_mode
|
||||
resp = create_response_for_post_notification(
|
||||
notification_id=notification_id,
|
||||
client_reference=form.get('reference', None),
|
||||
template_id=template.id,
|
||||
template_version=template._template['version'],
|
||||
template_version=template.version,
|
||||
service_id=service.id,
|
||||
notification_type=notification_type,
|
||||
reply_to=reply_to_text,
|
||||
scheduled_for=form.get("scheduled_for", None),
|
||||
template_with_content=template)
|
||||
template_with_content=template_with_content)
|
||||
|
||||
if str(service.id) in current_app.config.get('HIGH_VOLUME_SERVICE') and api_key.key_type == KEY_TYPE_NORMAL \
|
||||
and notification_type == EMAIL_TYPE:
|
||||
@@ -229,7 +238,7 @@ def process_sms_or_email_notification(
|
||||
persist_notification(
|
||||
notification_id=notification_id,
|
||||
template_id=template.id,
|
||||
template_version=template._template['version'],
|
||||
template_version=template.version,
|
||||
recipient=form_send_to,
|
||||
service=service,
|
||||
personalisation=personalisation,
|
||||
@@ -276,7 +285,7 @@ def save_email_to_queue(
|
||||
data = {
|
||||
"id": notification_id,
|
||||
"template_id": str(template.id),
|
||||
"template_version": template._template['version'],
|
||||
"template_version": template.version,
|
||||
"to": form['email_address'],
|
||||
"service_id": str(service_id),
|
||||
"personalisation": personalisation,
|
||||
@@ -328,7 +337,7 @@ def process_document_uploads(personalisation_data, service, simulated=False):
|
||||
|
||||
|
||||
def process_letter_notification(
|
||||
*, letter_data, api_key, template, template_with_content, reply_to_text, precompiled=False
|
||||
*, letter_data, api_key, service, template, template_with_content, reply_to_text, precompiled=False
|
||||
):
|
||||
if api_key.key_type == KEY_TYPE_TEAM:
|
||||
raise BadRequestError(message='Cannot send letters with a team api key', status_code=403)
|
||||
@@ -339,6 +348,7 @@ def process_letter_notification(
|
||||
if precompiled:
|
||||
return process_precompiled_letter_notifications(letter_data=letter_data,
|
||||
api_key=api_key,
|
||||
service=service,
|
||||
template=template,
|
||||
reply_to_text=reply_to_text)
|
||||
|
||||
@@ -360,6 +370,7 @@ def process_letter_notification(
|
||||
queue = QueueNames.CREATE_LETTERS_PDF if not test_key else QueueNames.RESEARCH_MODE
|
||||
|
||||
notification = create_letter_notification(letter_data=letter_data,
|
||||
service=service,
|
||||
template=template,
|
||||
api_key=api_key,
|
||||
status=status,
|
||||
@@ -414,7 +425,7 @@ def validate_address(api_key, letter_data):
|
||||
)
|
||||
|
||||
|
||||
def process_precompiled_letter_notifications(*, letter_data, api_key, template, reply_to_text):
|
||||
def process_precompiled_letter_notifications(*, letter_data, api_key, service, template, reply_to_text):
|
||||
try:
|
||||
status = NOTIFICATION_PENDING_VIRUS_CHECK
|
||||
letter_content = base64.b64decode(letter_data['content'])
|
||||
@@ -422,6 +433,7 @@ def process_precompiled_letter_notifications(*, letter_data, api_key, template,
|
||||
raise BadRequestError(message='Cannot decode letter content (invalid base64 encoding)', status_code=400)
|
||||
|
||||
notification = create_letter_notification(letter_data=letter_data,
|
||||
service=service,
|
||||
template=template,
|
||||
api_key=api_key,
|
||||
status=status,
|
||||
@@ -460,7 +472,7 @@ def get_reply_to_text(notification_type, form, template):
|
||||
service_email_reply_to_id = form.get("email_reply_to_id", None)
|
||||
reply_to = check_service_email_reply_to_id(
|
||||
str(authenticated_service.id), service_email_reply_to_id, notification_type
|
||||
) or template.get_reply_to_text()
|
||||
) or template.reply_to_text
|
||||
|
||||
elif notification_type == SMS_TYPE:
|
||||
service_sms_sender_id = form.get("sms_sender_id", None)
|
||||
@@ -470,10 +482,10 @@ def get_reply_to_text(notification_type, form, template):
|
||||
if sms_sender_id:
|
||||
reply_to = try_validate_and_format_phone_number(sms_sender_id)
|
||||
else:
|
||||
reply_to = template.get_reply_to_text()
|
||||
reply_to = template.reply_to_text
|
||||
|
||||
elif notification_type == LETTER_TYPE:
|
||||
reply_to = template.get_reply_to_text()
|
||||
reply_to = template.reply_to_text
|
||||
|
||||
return reply_to
|
||||
|
||||
|
||||
Reference in New Issue
Block a user