mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-23 17:01:35 -05:00
The statsd code was added to the utils library a while ago, uses the statsd from the util library and therefore consolidates the code into once place.
185 lines
6.4 KiB
Python
185 lines
6.4 KiB
Python
from datetime import datetime, timedelta
|
|
import math
|
|
|
|
from flask import current_app
|
|
from notifications_utils.statsd_decorators import statsd
|
|
from requests import (
|
|
post as requests_post,
|
|
RequestException
|
|
)
|
|
from botocore.exceptions import ClientError as BotoClientError
|
|
|
|
from notifications_utils.s3 import s3upload
|
|
|
|
from app import notify_celery
|
|
from app.aws import s3
|
|
from app.config import QueueNames, TaskNames
|
|
from app.dao.notifications_dao import (
|
|
get_notification_by_id,
|
|
update_notification_status_by_id,
|
|
dao_update_notification,
|
|
dao_get_notifications_by_references,
|
|
)
|
|
from app.models import NOTIFICATION_CREATED
|
|
|
|
LETTERS_PDF_FILE_LOCATION_STRUCTURE = \
|
|
'{folder}/NOTIFY.{reference}.{duplex}.{letter_class}.{colour}.{crown}.{date}.pdf'
|
|
|
|
|
|
def get_letter_pdf_filename(reference, crown):
|
|
now = datetime.utcnow()
|
|
|
|
print_datetime = now
|
|
if now.time() > current_app.config.get('LETTER_PROCESSING_DEADLINE'):
|
|
print_datetime = now + timedelta(days=1)
|
|
|
|
upload_file_name = LETTERS_PDF_FILE_LOCATION_STRUCTURE.format(
|
|
folder=print_datetime.date(),
|
|
reference=reference,
|
|
duplex="D",
|
|
letter_class="2",
|
|
colour="C",
|
|
crown="C" if crown else "N",
|
|
date=now.strftime('%Y%m%d%H%M%S')
|
|
).upper()
|
|
|
|
return upload_file_name
|
|
|
|
|
|
@notify_celery.task(bind=True, name="create-letters-pdf", max_retries=15, default_retry_delay=300)
|
|
@statsd(namespace="tasks")
|
|
def create_letters_pdf(self, notification_id):
|
|
try:
|
|
notification = get_notification_by_id(notification_id, _raise=True)
|
|
|
|
pdf_data, billable_units = get_letters_pdf(
|
|
notification.template,
|
|
contact_block=notification.reply_to_text,
|
|
org_id=notification.service.dvla_organisation.id,
|
|
values=notification.personalisation
|
|
)
|
|
current_app.logger.info("PDF Letter {} reference {} created at {}, {} bytes".format(
|
|
notification.id, notification.reference, notification.created_at, len(pdf_data)))
|
|
|
|
upload_file_name = get_letter_pdf_filename(
|
|
notification.reference, notification.service.crown)
|
|
|
|
s3upload(
|
|
filedata=pdf_data,
|
|
region=current_app.config['AWS_REGION'],
|
|
bucket_name=current_app.config['LETTERS_PDF_BUCKET_NAME'],
|
|
file_location=upload_file_name
|
|
)
|
|
|
|
current_app.logger.info("Uploaded letters PDF {} to {}".format(
|
|
upload_file_name, current_app.config['LETTERS_PDF_BUCKET_NAME']))
|
|
|
|
notification.billable_units = billable_units
|
|
dao_update_notification(notification)
|
|
|
|
current_app.logger.info(
|
|
'Letter notification reference {reference}: billable units set to {billable_units}'.format(
|
|
reference=str(notification.reference), billable_units=billable_units))
|
|
|
|
except (RequestException, BotoClientError):
|
|
try:
|
|
current_app.logger.exception(
|
|
"Letters PDF notification creation for id: {} failed".format(notification_id)
|
|
)
|
|
self.retry(queue=QueueNames.RETRY)
|
|
except self.MaxRetriesExceededError:
|
|
current_app.logger.exception(
|
|
"RETRY FAILED: task create_letters_pdf failed for notification {}".format(notification_id),
|
|
)
|
|
update_notification_status_by_id(notification_id, 'technical-failure')
|
|
|
|
|
|
def get_letters_pdf(template, contact_block, org_id, values):
|
|
template_for_letter_print = {
|
|
"subject": template.subject,
|
|
"content": template.content
|
|
}
|
|
|
|
data = {
|
|
'letter_contact_block': contact_block,
|
|
'template': template_for_letter_print,
|
|
'values': values,
|
|
'dvla_org_id': org_id,
|
|
}
|
|
resp = requests_post(
|
|
'{}/print.pdf'.format(
|
|
current_app.config['TEMPLATE_PREVIEW_API_HOST']
|
|
),
|
|
json=data,
|
|
headers={'Authorization': 'Token {}'.format(current_app.config['TEMPLATE_PREVIEW_API_KEY'])}
|
|
)
|
|
resp.raise_for_status()
|
|
|
|
pages_per_sheet = 2
|
|
billable_units = math.ceil(int(resp.headers.get("X-pdf-page-count", 0)) / pages_per_sheet)
|
|
|
|
return resp.content, billable_units
|
|
|
|
|
|
@notify_celery.task(name='collate-letter-pdfs-for-day')
|
|
def collate_letter_pdfs_for_day(date):
|
|
letter_pdfs = s3.get_s3_bucket_objects(
|
|
current_app.config['LETTERS_PDF_BUCKET_NAME'],
|
|
subfolder=date
|
|
)
|
|
for letters in group_letters(letter_pdfs):
|
|
filenames = [letter['Key'] for letter in letters]
|
|
current_app.logger.info(
|
|
'Calling task zip-and-send-letter-pdfs for {} pdfs of total size {:,} bytes'.format(
|
|
len(filenames),
|
|
sum(letter['Size'] for letter in letters)
|
|
)
|
|
)
|
|
notify_celery.send_task(
|
|
name=TaskNames.ZIP_AND_SEND_LETTER_PDFS,
|
|
kwargs={'filenames_to_zip': filenames},
|
|
queue=QueueNames.PROCESS_FTP,
|
|
compression='zlib'
|
|
)
|
|
|
|
|
|
def group_letters(letter_pdfs):
|
|
"""
|
|
Group letters in chunks of MAX_LETTER_PDF_ZIP_FILESIZE. Will add files to lists, never going over that size.
|
|
If a single file is (somehow) larger than MAX_LETTER_PDF_ZIP_FILESIZE that'll be in a list on it's own.
|
|
If there are no files, will just exit (rather than yielding an empty list).
|
|
"""
|
|
running_filesize = 0
|
|
list_of_files = []
|
|
for letter in letter_pdfs:
|
|
if letter['Key'].lower().endswith('.pdf') and letter_in_created_state(letter['Key']):
|
|
if (
|
|
running_filesize + letter['Size'] > current_app.config['MAX_LETTER_PDF_ZIP_FILESIZE'] or
|
|
len(list_of_files) >= current_app.config['MAX_LETTER_PDF_COUNT_PER_ZIP']
|
|
):
|
|
yield list_of_files
|
|
running_filesize = 0
|
|
list_of_files = []
|
|
|
|
running_filesize += letter['Size']
|
|
list_of_files.append(letter)
|
|
|
|
if list_of_files:
|
|
yield list_of_files
|
|
|
|
|
|
def letter_in_created_state(filename):
|
|
# filename looks like '2018-01-13/NOTIFY.ABCDEF1234567890.D.2.C.C.20180113120000.PDF'
|
|
subfolder = filename.split('/')[0]
|
|
ref = filename.split('.')[1]
|
|
notifications = dao_get_notifications_by_references([ref])
|
|
if notifications:
|
|
if notifications[0].status == NOTIFICATION_CREATED:
|
|
return True
|
|
current_app.logger.info('Collating letters for {} but notification with reference {} already in {}'.format(
|
|
subfolder,
|
|
ref,
|
|
notifications[0].status
|
|
))
|
|
return False
|