import json
from datetime import datetime, timedelta
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from django.utils.safestring import SafeString
from fcm_django.models import FCMDevice
from post_office import mail as po_email
from accounts.models import User
from cobalt.settings import (
DEFAULT_FROM_EMAIL,
GLOBAL_TITLE,
)
from masterpoints.views import user_summary
from notifications.models import (
EmailBatchRBAC,
BatchID,
Snooper,
RealtimeNotificationHeader,
RealtimeNotification,
)
from notifications.views.core import _cloudwatch_reader, send_fcm_message
from rbac.core import rbac_user_has_role
from rbac.decorators import rbac_check_role
from rbac.views import rbac_forbidden
from utils.utils import cobalt_paginator
from post_office.models import Email as PostOfficeEmail
[docs]
@login_required()
def admin_view_all_emails(request):
"""Show email notifications for administrators"""
# check access
role = "notifications.admin.view"
if not rbac_user_has_role(request.user, role):
return rbac_forbidden(request, role)
emails = PostOfficeEmail.objects.all().select_related("snooper").order_by("-pk")
things = cobalt_paginator(request, emails)
return render(
request, "notifications/admin_view_all_emails.html", {"things": things}
)
[docs]
@login_required()
def admin_view_email_by_batch(request, batch_id):
"""Show an email from a batch"""
# NOTE: the batch_id parameter passed in is the key to the
# EmailBatchRBAC table, NOT the key to the BatchID table
# Prior to April 24 it was being used for both, which only
# worked because the two were identical in production.
# This can not be guarenteed.
batch = get_object_or_404(EmailBatchRBAC, pk=batch_id)
admin_role = "notifications.admin.view"
if not (
rbac_user_has_role(request.user, batch.rbac_role)
or rbac_user_has_role(request.user, admin_role)
):
return rbac_forbidden(request, batch.rbac_role)
# snoopers = Snooper.objects.filter(batch_id=batch_id)
snoopers = Snooper.objects.filter(batch_id=batch.batch_id)
if not snoopers:
return HttpResponse("Not found")
# COB-793
return render(
request,
"notifications/admin_view_email.html",
{
"email": snoopers.first().post_office_email,
"snoopers": snoopers,
"snooper": snoopers.first(),
},
)
[docs]
@login_required()
def admin_view_email(request, email_id):
"""Show single email for administrators"""
email = get_object_or_404(PostOfficeEmail, pk=email_id)
# check access
snooper = (
Snooper.objects.select_related("batch_id")
.filter(post_office_email=email)
.first()
)
admin_role = "notifications.admin.view"
try:
rbac_role = (
EmailBatchRBAC.objects.filter(batch_id=snooper.batch_id).first().rbac_role
)
except AttributeError:
rbac_role = admin_role
if not (
rbac_user_has_role(request.user, rbac_role)
or rbac_user_has_role(request.user, admin_role)
):
return rbac_forbidden(request, rbac_role)
return render(
request,
"notifications/admin_view_email.html",
{"email": email, "snooper": snooper},
)
[docs]
@login_required()
def admin_send_email_copy_to_admin(request, email_id):
"""Send a copy of an email to an admin so they can see it fully rendered
With using templates for Django post office emails and render_on_delivery,
we no longer have a copy of the email. We can regenerate it though by
sending to someone else.
"""
# check access
role = "notifications.admin.view"
if not rbac_user_has_role(request.user, role):
return rbac_forbidden(request, role)
email = get_object_or_404(PostOfficeEmail, pk=email_id)
# DEFAULT_FROM_EMAIL could be 'a@b.com' or 'something something<a@b.com>'
if DEFAULT_FROM_EMAIL.find("<") >= 0:
parts = DEFAULT_FROM_EMAIL.split("<")
from_name = f"Email Copy from {GLOBAL_TITLE}<{parts[1]}"
else:
from_name = f"Email Copy from {GLOBAL_TITLE}<{DEFAULT_FROM_EMAIL}>"
# We don't send this through the normal method
po_email.send(
request.user.email,
from_name,
template=email.template,
context=email.context,
render_on_delivery=True,
priority="now",
)
return HttpResponse("Message sent. Check your inbox.")
@rbac_check_role("notifications.realtime_send.edit")
def admin_view_realtime_notifications(request):
"""Allow an admin to see their notifications
Args:
request (HTTPRequest): standard request object
Returns:
HTTPResponse
"""
notification_headers = RealtimeNotificationHeader.objects.filter(
admin=request.user
).order_by("-pk")
things = cobalt_paginator(request, notification_headers)
return render(request, "notifications/admin_view_realtime.html", {"things": things})
@rbac_check_role("notifications.admin.view")
def global_admin_view_realtime_notifications(request):
"""Allow a global admin to see all real time notifications
Args:
request (HTTPRequest): standard request object
Returns:
HTTPResponse
"""
notification_headers = RealtimeNotificationHeader.objects.order_by("-pk")
things = cobalt_paginator(request, notification_headers)
return render(request, "notifications/admin_view_realtime.html", {"things": things})
@rbac_check_role("notifications.realtime_send.edit", "notifications.admin.view")
def admin_view_realtime_notification_detail(request, header_id):
"""Show the detail of a batch of messages. Actually allows anyone with
notifications.realtime_send.edit to see any batch, but that is okay.
Args:
request (HTTPRequest): standard request object
header_id (int): id of the RealtimeNotificationHeader to show
Returns:
HTTPResponse
"""
notification_header = get_object_or_404(RealtimeNotificationHeader, pk=header_id)
# Convert string to json
notification_header.uncontactable_users = (
notification_header.get_uncontactable_users()
)
notification_header.unregistered_users = (
notification_header.get_unregistered_users()
)
notification_header.invalid_lines = notification_header.get_invalid_lines()
notifications = RealtimeNotification.objects.filter(
header=notification_header
).select_related("member")
# Get sent by FCM
return render(
request,
"notifications/admin_view_realtime_detail.html",
{"notification_header": notification_header, "notifications": notifications},
)
@rbac_check_role("notifications.realtime_send.edit", "notifications.admin.view")
def admin_view_realtime_notification_item(request, notification_id):
"""Show the detail of a single message. Actually allows anyone with
notifications.realtime_send.edit to see the message, but that is okay.
Args:
request (HTTPRequest): standard request object
notification_id (int): id of the RealtimeNotification to show
Returns:
HTTPResponse
"""
notification = get_object_or_404(RealtimeNotification, pk=notification_id)
if notification.fcm_device:
return _admin_view_realtime_notification_item_fcm(request, notification)
else:
return _admin_view_realtime_notification_item_sms(request, notification)
def _admin_view_realtime_notification_item_sms(request, notification):
"""Sub to handle specifics of SMS.
For SMS we save the AWS Message Id when we send the message, this looks in the
AWS Cloudwatch logs to find out what happened subsequently.
Args:
request (HTTPRequest): standard request object
notification (RealtimeNotification): RealtimeNotification to show
Returns:
HTTPResponse
"""
# TODO: Move this to a global variable
success_log_group = "sns/ap-southeast-2/730536189139/DirectPublishToPhoneNumber"
error_log_group = (
"sns/ap-southeast-2/730536189139/DirectPublishToPhoneNumber/Failure"
)
success_results = _cloudwatch_reader(success_log_group, notification)
if success_results:
results = success_results
successful = True
else: # Try for errors, format is the same
results = _cloudwatch_reader(error_log_group, notification)
successful = False
if results:
message = results[0]["message"]
message_json = json.loads(message)
delivery = message_json["delivery"]
cloudwatch = SafeString(f"<pre>{json.dumps(delivery, indent=4)}</pre>")
provider_response = delivery["providerResponse"]
else:
cloudwatch = "No data found"
provider_response = "No data found"
raw_cloudwatch = SafeString(f"<pre>{json.dumps(results, indent=4)}</pre>")
return render(
request,
"notifications/admin_view_realtime_item.html",
{
"notification": notification,
"provider_response": provider_response,
"cloudwatch": cloudwatch,
"raw_cloudwatch": raw_cloudwatch,
"successful": successful,
},
)
def _admin_view_realtime_notification_item_fcm(request, notification):
"""Sub to handle specifics of FCM.
Args:
request (HTTPRequest): standard request object
notification (RealtimeNotification): RealtimeNotification to show
Returns:
HTTPResponse
"""
return render(
request,
"notifications/admin_view_realtime_item.html",
{
"notification": notification,
},
)
@rbac_check_role("notifications.admin.view")
def global_admin_view_emails(request, member_id):
"""Allow an admin to see emails for a player
Args:
member_id: member to look up
request (HTTPRequest): standard request object
Returns:
HTTPResponse
"""
member = get_object_or_404(User, pk=member_id)
summary = user_summary(member.system_number)
one_year_ago = timezone.now() - timedelta(days=365)
# emails are indexed by created date, so this may help performance
last_year_email = PostOfficeEmail.objects.filter(created__gte=one_year_ago)
email_list = last_year_email.filter(to=[member.email]).order_by("-pk")[:50]
# email_list = PostOfficeEmail.objects.filter(to=[member.email]).order_by("-pk")[:50]
return render(
request,
"notifications/global_admin_view_emails.html",
{
"profile": member,
"summary": summary,
"emails": email_list,
},
)
@rbac_check_role("notifications.admin.view", "notifications.realtime_send.edit")
def global_admin_view_real_time_for_user(request, member_id):
"""Allow an admin to see real time notifications for a player
Args:
member_id: member to look up
request (HTTPRequest): standard request object
Returns:
HTTPResponse
"""
member = get_object_or_404(User, pk=member_id)
summary = user_summary(member.system_number)
real_time_list = RealtimeNotification.objects.filter(member=member).order_by(
"-created_time"
)
return render(
request,
"notifications/global_admin_view_real_time_for_user.html",
{
"profile": member,
"summary": summary,
"real_time_list": real_time_list,
},
)
[docs]
def notifications_status_summary():
"""Used by utils status to get a status of notifications"""
latest = PostOfficeEmail.objects.all().order_by("-id").first()
pending = PostOfficeEmail.objects.filter(status=2).count()
last_hour_date_time = timezone.now() - timedelta(hours=1)
last_hour = PostOfficeEmail.objects.filter(created__gt=last_hour_date_time).count()
return {"latest": latest, "pending": pending, "last_hour": last_hour}
@rbac_check_role("system.admin.edit")
def admin_send_test_fcm_message(request):
"""Send a test message to anyone"""
message = ""
if request.method == "POST":
user = User.objects.filter(system_number=request.POST.get("abf")).first()
msg = request.POST.get("msg")
try:
fcm_device = FCMDevice.objects.filter(user=user).latest("pk")
send_fcm_message(fcm_device, msg, admin=request.user)
message = f"Message sent to {user} on {fcm_device}"
except Exception as exc:
message = exc.__str__()
return render(
request, "notifications/admin_send_test_fcm_message.html", {"message": message}
)