updates to views.py
This commit is contained in:
parent
0d9aa2f4a7
commit
4a2e9342ea
|
@ -1,46 +1,30 @@
|
|||
import json
|
||||
|
||||
import boto3
|
||||
import pytz
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
from urllib.error import URLError
|
||||
except ImportError:
|
||||
from urllib2 import urlopen, URLError
|
||||
import copy
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||||
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from django.views.decorators.http import require_POST
|
||||
import boto3
|
||||
import pytz
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
from . import settings
|
||||
from . import signals
|
||||
from . import utils
|
||||
from .models import (
|
||||
BounceRecord,
|
||||
ComplaintRecord,
|
||||
SendRecord,
|
||||
UnknownRecord,
|
||||
AwsSesUserAddon
|
||||
)
|
||||
from .models import BounceRecord, ComplaintRecord, SendRecord, UnknownRecord, AwsSesUserAddon
|
||||
|
||||
logger = settings.logger
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def superuser_only(view_func):
|
||||
"""
|
||||
Limit a view to superuser only.
|
||||
"""
|
||||
"""Decorator to restrict a view to superusers only."""
|
||||
def _inner(request, *args, **kwargs):
|
||||
if not request.user.is_superuser:
|
||||
raise PermissionDenied
|
||||
|
@ -49,66 +33,73 @@ def superuser_only(view_func):
|
|||
|
||||
|
||||
def stats_to_list(stats_dict, localize=pytz):
|
||||
"""Parse SES send statistics into an ordered list of 15-minute summaries.
|
||||
|
||||
Args:
|
||||
stats_dict (dict): Raw SES statistics data.
|
||||
localize (module): Timezone module (default: pytz).
|
||||
|
||||
Returns:
|
||||
list: Sorted list of datapoints with localized timestamps.
|
||||
"""
|
||||
Parse the output of ``SESConnection.get_send_statistics()`` in to an
|
||||
ordered list of 15-minute summaries.
|
||||
"""
|
||||
# Make a copy, so we don't change the original stats_dict.
|
||||
result = copy.deepcopy(stats_dict)
|
||||
datapoints = []
|
||||
if localize:
|
||||
current_tz = localize.timezone(settings.TIME_ZONE)
|
||||
else:
|
||||
current_tz = None
|
||||
for dp in result['SendDataPoints']:
|
||||
current_tz = localize.timezone(settings.TIME_ZONE) if localize else None
|
||||
|
||||
for dp in stats_dict['SendDataPoints']:
|
||||
if current_tz:
|
||||
utc_dt = dp['Timestamp']
|
||||
dp['Timestamp'] = current_tz.normalize(
|
||||
utc_dt.astimezone(current_tz))
|
||||
dp['Timestamp'] = current_tz.normalize(utc_dt.astimezone(current_tz))
|
||||
datapoints.append(dp)
|
||||
|
||||
datapoints.sort(key=lambda x: x['Timestamp'])
|
||||
|
||||
return datapoints
|
||||
return sorted(datapoints, key=lambda x: x['Timestamp'])
|
||||
|
||||
|
||||
def emails_parse(emails_dict):
|
||||
"""Parse SES verified email addresses into a sorted list.
|
||||
|
||||
Args:
|
||||
emails_dict (dict): Raw SES verified email data.
|
||||
|
||||
Returns:
|
||||
list: Sorted list of verified email addresses.
|
||||
"""
|
||||
Parse the output of ``SESConnection.list_verified_emails()`` and get
|
||||
a list of emails.
|
||||
"""
|
||||
return sorted([email for email in emails_dict['VerifiedEmailAddresses']])
|
||||
return sorted(emails_dict['VerifiedEmailAddresses'])
|
||||
|
||||
|
||||
def sum_stats(stats_data):
|
||||
"""
|
||||
Summarize the bounces, complaints, delivery attempts and rejects from a
|
||||
list of datapoints.
|
||||
"""
|
||||
t_bounces = 0
|
||||
t_complaints = 0
|
||||
t_delivery_attempts = 0
|
||||
t_rejects = 0
|
||||
for dp in stats_data:
|
||||
t_bounces += dp['Bounces']
|
||||
t_complaints += dp['Complaints']
|
||||
t_delivery_attempts += dp['DeliveryAttempts']
|
||||
t_rejects += dp['Rejects']
|
||||
"""Summarize SES statistics from a list of datapoints.
|
||||
|
||||
return {
|
||||
'Bounces': t_bounces,
|
||||
'Complaints': t_complaints,
|
||||
'DeliveryAttempts': t_delivery_attempts,
|
||||
'Rejects': t_rejects,
|
||||
Args:
|
||||
stats_data (list): List of SES datapoints.
|
||||
|
||||
Returns:
|
||||
dict: Summary of bounces, complaints, delivery attempts, and rejects.
|
||||
"""
|
||||
summary = {
|
||||
'Bounces': 0,
|
||||
'Complaints': 0,
|
||||
'DeliveryAttempts': 0,
|
||||
'Rejects': 0,
|
||||
}
|
||||
for dp in stats_data:
|
||||
summary['Bounces'] += dp['Bounces']
|
||||
summary['Complaints'] += dp['Complaints']
|
||||
summary['DeliveryAttempts'] += dp['DeliveryAttempts']
|
||||
summary['Rejects'] += dp['Rejects']
|
||||
return summary
|
||||
|
||||
|
||||
@superuser_only
|
||||
def dashboard(request):
|
||||
"""Display SES send statistics dashboard for superusers.
|
||||
|
||||
Args:
|
||||
request: HTTP request object.
|
||||
|
||||
Returns:
|
||||
HttpResponse: Rendered dashboard with SES statistics.
|
||||
"""
|
||||
Graph SES send statistics over time.
|
||||
"""
|
||||
cache_key = 'vhash:django_aws_ses_status'
|
||||
cache_key = 'django_aws_ses_status'
|
||||
cached_view = cache.get(cache_key)
|
||||
if cached_view:
|
||||
return cached_view
|
||||
|
@ -118,337 +109,220 @@ def dashboard(request):
|
|||
aws_access_key_id=settings.ACCESS_KEY,
|
||||
aws_secret_access_key=settings.SECRET_KEY,
|
||||
region_name=settings.AWS_SES_REGION_NAME,
|
||||
endpoint_url=settings.AWS_SES_REGION_ENDPOINT_URL,
|
||||
endpoint_url=settings.AWS_SES_REGION_ENDPOINT,
|
||||
)
|
||||
|
||||
quota_dict = ses_conn.get_send_quota()
|
||||
verified_emails_dict = ses_conn.list_verified_email_addresses()
|
||||
stats = ses_conn.get_send_statistics()
|
||||
try:
|
||||
quota_dict = ses_conn.get_send_quota()
|
||||
verified_emails_dict = ses_conn.list_verified_email_addresses()
|
||||
stats = ses_conn.get_send_statistics()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch SES statistics: {e}")
|
||||
return HttpResponseBadRequest("Failed to fetch SES statistics")
|
||||
|
||||
verified_emails = emails_parse(verified_emails_dict)
|
||||
ordered_data = stats_to_list(stats)
|
||||
summary = sum_stats(ordered_data)
|
||||
|
||||
extra_context = {
|
||||
context = {
|
||||
'title': 'SES Statistics',
|
||||
'datapoints': ordered_data,
|
||||
'24hour_quota': quota_dict['Max24HourSend'],
|
||||
'24hour_sent': quota_dict['SentLast24Hours'],
|
||||
'24hour_remaining':
|
||||
quota_dict['Max24HourSend'] -
|
||||
quota_dict['SentLast24Hours'],
|
||||
'24hour_remaining': quota_dict['Max24HourSend'] - quota_dict['SentLast24Hours'],
|
||||
'persecond_rate': quota_dict['MaxSendRate'],
|
||||
'verified_emails': verified_emails,
|
||||
'summary': summary,
|
||||
'access_key': settings.ACCESS_KEY,
|
||||
'local_time': True,
|
||||
}
|
||||
|
||||
response = render(request, 'django_aws_ses/send_stats.html', extra_context)
|
||||
|
||||
response = render(request, 'django_aws_ses/send_stats.html', context)
|
||||
cache.set(cache_key, response, 60 * 15) # Cache for 15 minutes
|
||||
return response
|
||||
|
||||
|
||||
@require_POST
|
||||
def handle_bounce(request):
|
||||
"""Handle AWS SES/SNS bounce, complaint, or delivery notifications.
|
||||
|
||||
Args:
|
||||
request: HTTP request object with SNS notification JSON.
|
||||
|
||||
Returns:
|
||||
HttpResponse: HTTP 200 for successful processing, 400 for invalid JSON.
|
||||
"""
|
||||
Handle a bounced email via an SNS webhook.
|
||||
|
||||
Parse the bounced message and send the appropriate signal.
|
||||
For bounce messages the bounce_received signal is called.
|
||||
For complaint messages the complaint_received signal is called.
|
||||
See: http://docs.aws.amazon.com/sns/latest/gsg/json-formats.html#http-subscription-confirmation-json
|
||||
See: http://docs.amazonwebservices.com/ses/latest/DeveloperGuide/NotificationsViaSNS.html
|
||||
|
||||
In addition to email bounce requests this endpoint also supports the SNS
|
||||
subscription confirmation request. This request is sent to the SNS
|
||||
subscription endpoint when the subscription is registered.
|
||||
See: http://docs.aws.amazon.com/sns/latest/gsg/Subscribe.html
|
||||
|
||||
For the format of the SNS subscription confirmation request see this URL:
|
||||
http://docs.aws.amazon.com/sns/latest/gsg/json-formats.html#http-subscription-confirmation-json
|
||||
|
||||
SNS message signatures are verified by default. This functionality can
|
||||
be disabled by setting AWS_SES_VERIFY_BOUNCE_SIGNATURES to False.
|
||||
However, this is not recommended.
|
||||
See: http://docs.amazonwebservices.com/sns/latest/gsg/SendMessageToHttp.verify.signature.html
|
||||
"""
|
||||
logger.warning(u'Received SNS call back')
|
||||
|
||||
|
||||
raw_json = request.body
|
||||
logger.info("Received SNS callback")
|
||||
|
||||
try:
|
||||
notification = json.loads(raw_json.decode('utf-8'))
|
||||
except ValueError as e:
|
||||
# TODO: What kind of response should be returned here?
|
||||
logger.warning(u'Received bounce with bad JSON: "%s"', e)
|
||||
return HttpResponseBadRequest()
|
||||
notification = json.loads(request.body.decode('utf-8'))
|
||||
except (ValueError, UnicodeDecodeError) as e:
|
||||
logger.warning(f"Invalid SNS notification JSON: {e}")
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
|
||||
# Verify the authenticity of the bounce message.
|
||||
if (settings.VERIFY_BOUNCE_SIGNATURES and
|
||||
not utils.verify_bounce_message(notification)):
|
||||
# Don't send any info back when the notification is not
|
||||
# verified. Simply, don't process it.
|
||||
logger.info(
|
||||
u'Received unverified notification: Type: %s',
|
||||
notification.get('Type'),
|
||||
extra={
|
||||
'notification': notification,
|
||||
},
|
||||
)
|
||||
if settings.VERIFY_BOUNCE_SIGNATURES and not utils.verify_bounce_message(notification):
|
||||
logger.warning(f"Unverified SNS notification: Type={notification.get('Type')}")
|
||||
return HttpResponse()
|
||||
logger.info('notification.get("Type"): %s' % notification.get("Type"))
|
||||
if notification.get('Type') in ('SubscriptionConfirmation',
|
||||
'UnsubscribeConfirmation'):
|
||||
# Process the (un)subscription confirmation.
|
||||
|
||||
logger.info(
|
||||
u'Received subscription confirmation: TopicArn: %s',
|
||||
notification.get('TopicArn'),
|
||||
extra={
|
||||
'notification': notification,
|
||||
},
|
||||
)
|
||||
|
||||
# Get the subscribe url and hit the url to confirm the subscription.
|
||||
notification_type = notification.get('Type')
|
||||
if notification_type in ('SubscriptionConfirmation', 'UnsubscribeConfirmation'):
|
||||
logger.info(f"Received {notification_type}: TopicArn={notification.get('TopicArn')}")
|
||||
subscribe_url = notification.get('SubscribeURL')
|
||||
try:
|
||||
urlopen(subscribe_url).read()
|
||||
except URLError as e:
|
||||
# Some kind of error occurred when confirming the request.
|
||||
logger.error(
|
||||
u'Could not confirm subscription: "%s"', e,
|
||||
extra={
|
||||
'notification': notification,
|
||||
},
|
||||
exc_info=True,
|
||||
if subscribe_url:
|
||||
try:
|
||||
import requests
|
||||
response = requests.get(subscribe_url)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Failed to confirm {notification_type}: {e}")
|
||||
return HttpResponse()
|
||||
|
||||
if notification_type != 'Notification':
|
||||
UnknownRecord.objects.create(event_type=notification_type, aws_data=str(notification))
|
||||
logger.info(f"Received unknown notification type: {notification_type}")
|
||||
return HttpResponse()
|
||||
|
||||
try:
|
||||
message = json.loads(notification['Message'])
|
||||
except ValueError as e:
|
||||
logger.warning(f"Invalid message JSON in notification: {e}")
|
||||
return HttpResponse()
|
||||
|
||||
mail_obj = message.get('mail', {})
|
||||
event_type = message.get('notificationType', message.get('eventType', 'Unknown'))
|
||||
|
||||
if event_type == 'Bounce':
|
||||
bounce_obj = message.get('bounce', {})
|
||||
feedback_id = bounce_obj.get('feedbackId')
|
||||
bounce_type = bounce_obj.get('bounceType')
|
||||
bounce_subtype = bounce_obj.get('bounceSubType')
|
||||
logger.info(f"Received bounce: feedbackId={feedback_id}, type={bounce_type}, subtype={bounce_subtype}")
|
||||
|
||||
for recipient in bounce_obj.get('bouncedRecipients', []):
|
||||
BounceRecord.objects.create(
|
||||
email=recipient.get('emailAddress'),
|
||||
status=recipient.get('status'),
|
||||
action=recipient.get('action'),
|
||||
diagnostic_code=recipient.get('diagnosticCode'),
|
||||
bounce_type=bounce_type,
|
||||
bounce_sub_type=bounce_subtype,
|
||||
feedback_id=feedback_id,
|
||||
reporting_mta=bounce_obj.get('reportingMTA'),
|
||||
)
|
||||
elif notification.get('Type') == 'Notification':
|
||||
try:
|
||||
message = json.loads(notification['Message'])
|
||||
except ValueError as e:
|
||||
# The message isn't JSON.
|
||||
# Just ignore the notification.
|
||||
logger.warning(u'Received bounce with bad JSON: "%s"', e, extra={
|
||||
'notification': notification,
|
||||
})
|
||||
|
||||
else:
|
||||
|
||||
mail_obj = message.get('mail')
|
||||
event_type = message.get('notificationType', message.get('eventType'))
|
||||
logger.info('event_type: %s' % event_type)
|
||||
if event_type == 'Bounce':
|
||||
# Bounce
|
||||
bounce_obj = message.get('bounce', {})
|
||||
|
||||
# Logging
|
||||
feedback_id = bounce_obj.get('feedbackId')
|
||||
bounce_type = bounce_obj.get('bounceType')
|
||||
bounce_subtype = bounce_obj.get('bounceSubType')
|
||||
bounce_recipients = bounce_obj.get('bouncedRecipients', [])
|
||||
logger.info(
|
||||
u'Received bounce notification: feedbackId: %s, bounceType: %s, bounceSubType: %s',
|
||||
feedback_id, bounce_type, bounce_subtype,
|
||||
extra={
|
||||
'notification': notification,
|
||||
},
|
||||
)
|
||||
|
||||
# create a BounceRecord so we can keep from sending to bad emails.
|
||||
logger.info('create records')
|
||||
for recipient in bounce_recipients:
|
||||
logger.info('recipient: %s' % recipient)
|
||||
BounceRecord.objects.create(
|
||||
email = recipient.get('emailAddress', None),
|
||||
status = recipient.get('status', None),
|
||||
action = recipient.get('action', None),
|
||||
diagnostic_code = recipient.get('diagnosticCode', None),
|
||||
bounce_type = bounce_obj.get('bounceType', None),
|
||||
bounce_sub_type = bounce_obj.get('bounceSubType', None),
|
||||
feedback_id = bounce_obj.get('feedbackId', None),
|
||||
reporting_mta = bounce_obj.get('reportingMTA', None),
|
||||
)
|
||||
|
||||
|
||||
signals.bounce_received.send(
|
||||
sender=handle_bounce,
|
||||
mail_obj=mail_obj,
|
||||
bounce_obj=bounce_obj,
|
||||
raw_message=raw_json,
|
||||
)
|
||||
|
||||
elif event_type == 'Complaint':
|
||||
# Complaint
|
||||
complaint_obj = message.get('complaint', {})
|
||||
|
||||
# Logging
|
||||
feedback_id = complaint_obj.get('feedbackId')
|
||||
feedback_type = complaint_obj.get('complaintFeedbackType')
|
||||
complaint_recipients = complaint_obj.get('complainedRecipients')
|
||||
logger.info('create records')
|
||||
for recipient in complaint_recipients:
|
||||
logger.info('recipient: %s' % recipient)
|
||||
ComplaintRecord.objects.create(
|
||||
email = recipient.get('emailAddress', None),
|
||||
sub_type = complaint_obj.get('complaintSubType', None),
|
||||
feedback_id = complaint_obj.get('feedbackId', None),
|
||||
feedback_type = complaint_obj.get('complaintFeedbackType', None),
|
||||
)
|
||||
|
||||
logger.info(
|
||||
u'Received complaint notification: feedbackId: %s, feedbackType: %s',
|
||||
feedback_id, feedback_type,
|
||||
extra={
|
||||
'notification': notification,
|
||||
},
|
||||
)
|
||||
|
||||
signals.complaint_received.send(
|
||||
sender=handle_bounce,
|
||||
mail_obj=mail_obj,
|
||||
complaint_obj=complaint_obj,
|
||||
raw_message=raw_json,
|
||||
)
|
||||
|
||||
elif event_type in ['Delivery','Send']:
|
||||
# Delivery
|
||||
send_obj = message.get('mail', {})
|
||||
|
||||
logger.info('send_obj: %s' % send_obj)
|
||||
|
||||
source = send_obj.get('source', 'N/A')#settings.DEFAULT_FROM_EMAIL)
|
||||
destinations = send_obj.get('destination', [])
|
||||
message_id = send_obj.get('messageId','N/A')
|
||||
delivery = message.get('delivery', None)
|
||||
aws_process_time = -1
|
||||
smtp_response = 'N/A'
|
||||
if delivery:
|
||||
logger.info('we are a delivery and had a delivery key')
|
||||
aws_process_time = delivery.get('processingTimeMillis',0)
|
||||
smtp_response = delivery.get('smtpResponse', 'N/A')
|
||||
|
||||
common_headers = send_obj.get('commonHeaders', None)
|
||||
subject = "N/A"
|
||||
if common_headers:
|
||||
subject = common_headers.get('subject','N/A')
|
||||
status = event_type
|
||||
logger.info('create records')
|
||||
logger.info('destinations: %s' % destinations)
|
||||
for destination in destinations:
|
||||
try:
|
||||
logger.info('destination: %s' % destination)
|
||||
send_record, created = SendRecord.objects.get_or_create(
|
||||
source = source,
|
||||
destination = destination,
|
||||
status = status,
|
||||
message_id = message_id,
|
||||
defaults={
|
||||
"aws_process_time": aws_process_time,
|
||||
"smtp_response": smtp_response,
|
||||
"subject": subject
|
||||
}
|
||||
|
||||
)
|
||||
if send_record.subject == "N/A":
|
||||
send_record.subject = subject
|
||||
|
||||
if send_record.smtp_response == "N/A":
|
||||
send_record.smtp_response = smtp_response
|
||||
|
||||
if send_record.aws_process_time == -1:
|
||||
send_record.aws_process_time = aws_process_time
|
||||
|
||||
send_record.save()
|
||||
except Exception as e:
|
||||
logger.info("error well trying to get_or_create record: %s" % e)
|
||||
logger.info(
|
||||
u'Received delivery notification: messageId: %s',
|
||||
message_id,
|
||||
extra={
|
||||
'notification': notification,
|
||||
},
|
||||
)
|
||||
|
||||
signals.delivery_received.send(
|
||||
sender=handle_bounce,
|
||||
mail_obj=mail_obj,
|
||||
delivery_obj=send_obj,
|
||||
raw_message=raw_json,
|
||||
)
|
||||
|
||||
else:
|
||||
# We received an unknown notification type. Just log and
|
||||
# ignore it.
|
||||
|
||||
UnknownRecord.objects.create(
|
||||
event_type = eventType,
|
||||
aws_data = str(notification)
|
||||
)
|
||||
|
||||
logger.warning(u"Received unknown event", extra={
|
||||
'notification': notification,
|
||||
})
|
||||
else:
|
||||
|
||||
UnknownRecord.objects.create(
|
||||
eventType = notification.get('Type'),
|
||||
aws_data = str(notification)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
u'Received unknown notification type: %s',
|
||||
notification.get('Type'),
|
||||
extra={
|
||||
'notification': notification,
|
||||
},
|
||||
signals.bounce_received.send(
|
||||
sender=handle_bounce,
|
||||
mail_obj=mail_obj,
|
||||
bounce_obj=bounce_obj,
|
||||
raw_message=request.body,
|
||||
)
|
||||
|
||||
# AWS will consider anything other than 200 to be an error response and
|
||||
# resend the SNS request. We don't need that so we return 200 here.
|
||||
elif event_type == 'Complaint':
|
||||
complaint_obj = message.get('complaint', {})
|
||||
feedback_id = complaint_obj.get('feedbackId')
|
||||
feedback_type = complaint_obj.get('complaintFeedbackType')
|
||||
logger.info(f"Received complaint: feedbackId={feedback_id}, type={feedback_type}")
|
||||
|
||||
for recipient in complaint_obj.get('complainedRecipients', []):
|
||||
ComplaintRecord.objects.create(
|
||||
email=recipient.get('emailAddress'),
|
||||
sub_type=complaint_obj.get('complaintSubType'),
|
||||
feedback_id=feedback_id,
|
||||
feedback_type=feedback_type,
|
||||
)
|
||||
|
||||
signals.complaint_received.send(
|
||||
sender=handle_bounce,
|
||||
mail_obj=mail_obj,
|
||||
complaint_obj=complaint_obj,
|
||||
raw_message=request.body,
|
||||
)
|
||||
|
||||
elif event_type in ('Delivery', 'Send'):
|
||||
send_obj = mail_obj
|
||||
source = send_obj.get('source', settings.DEFAULT_FROM_EMAIL)
|
||||
destinations = send_obj.get('destination', [])
|
||||
message_id = send_obj.get('messageId', 'N/A')
|
||||
delivery = message.get('delivery', {})
|
||||
aws_process_time = delivery.get('processingTimeMillis', 0)
|
||||
smtp_response = delivery.get('smtpResponse', 'N/A')
|
||||
subject = send_obj.get('commonHeaders', {}).get('subject', 'N/A')
|
||||
|
||||
logger.info(f"Received {event_type} notification: messageId={message_id}")
|
||||
|
||||
for destination in destinations:
|
||||
try:
|
||||
send_record, created = SendRecord.objects.get_or_create(
|
||||
source=source,
|
||||
destination=destination,
|
||||
status=event_type,
|
||||
message_id=message_id,
|
||||
defaults={
|
||||
"aws_process_time": aws_process_time,
|
||||
"smtp_response": smtp_response,
|
||||
"subject": subject,
|
||||
}
|
||||
)
|
||||
if send_record.subject == "N/A":
|
||||
send_record.subject = subject
|
||||
if send_record.smtp_response == "N/A":
|
||||
send_record.smtp_response = smtp_response
|
||||
if send_record.aws_process_time == 0:
|
||||
send_record.aws_process_time = aws_process_time
|
||||
send_record.save()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save SendRecord for {destination}: {e}")
|
||||
|
||||
signals.delivery_received.send(
|
||||
sender=handle_bounce,
|
||||
mail_obj=mail_obj,
|
||||
delivery_obj=send_obj,
|
||||
raw_message=request.body,
|
||||
)
|
||||
|
||||
else:
|
||||
UnknownRecord.objects.create(event_type=event_type, aws_data=str(notification))
|
||||
logger.warning(f"Received unknown event: {event_type}")
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
class HandleUnsubscribe(TemplateView):
|
||||
|
||||
|
||||
class HandleUnsubscribe(TemplateView):
|
||||
"""View to handle email unsubscribe requests."""
|
||||
http_method_names = ['get']
|
||||
|
||||
template_name = settings.UNSUBSCRIBE_TEMPLET
|
||||
base_template_name = settings.BASE_TEMPLET
|
||||
unsubscribe_message = "We Have Unsubscribed the Following Email"
|
||||
|
||||
template_name = settings.UNSUBSCRIBE_TEMPLATE
|
||||
base_template_name = settings.BASE_TEMPLATE
|
||||
unsubscribe_message = "We Have Unsubscribed the Following Email"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Add base template and unsubscribe message to context."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
logger.info("in get_context_data ----- self.base_template_name: %s" % self.base_template_name)
|
||||
context['base_template_name'] = self.base_template_name
|
||||
context['unsubscribe_message'] = self.unsubscribe_message
|
||||
return context
|
||||
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Process unsubscribe request and redirect if invalid."""
|
||||
uuid = self.kwargs['uuid']
|
||||
hash = self.kwargs['hash']
|
||||
|
||||
logger.info("in get ----- self.base_template_name: %s" % self.base_template_name)
|
||||
|
||||
hash_value = self.kwargs['hash']
|
||||
|
||||
try:
|
||||
uuid = force_str(urlsafe_base64_decode(uuid).decode())
|
||||
logger.info('uuid: %s' % uuid)
|
||||
uuid = force_str(urlsafe_base64_decode(uuid))
|
||||
user = User.objects.get(pk=uuid)
|
||||
logger.info('user.pk: %s' % user.pk)
|
||||
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
|
||||
except (TypeError, ValueError, OverflowError, User.DoesNotExist) as e:
|
||||
logger.warning(f"Invalid unsubscribe UUID: {e}")
|
||||
return redirect(settings.HOME_URL)
|
||||
|
||||
try:
|
||||
ses = user.aws_ses
|
||||
except AwsSesUserAddon.DoesNotExist:
|
||||
ses = AwsSesUserAddon.objects.create(user=user)
|
||||
|
||||
if user is not None and user.aws_ses.check_unsubscribe_hash(hash):
|
||||
logger.info('ses.pk: %s' % ses.pk)
|
||||
|
||||
if user and ses.check_unsubscribe_hash(hash_value):
|
||||
ses.unsubscribe = True
|
||||
ses.save()
|
||||
logger.info(f"Unsubscribed user: {user.email}")
|
||||
else:
|
||||
logger.warning("bad hash was provided!")
|
||||
logger.warning(f"Invalid unsubscribe hash for user: {user.email}")
|
||||
return redirect(settings.HOME_URL)
|
||||
|
||||
return super(HandleUnsubscribe, self).get(request, *args, **kwargs)
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
Loading…
Reference in New Issue