Skip to content

Commit

Permalink
Merge pull request #452 from brunoamaral/improve-notifications
Browse files Browse the repository at this point in the history
Improve email notifications and add error logging
  • Loading branch information
brunoamaral authored Dec 23, 2024
2 parents 3ca0cac + e7c8292 commit be24a04
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 271 deletions.
144 changes: 74 additions & 70 deletions django/subscriptions/management/commands/send_admin_summary.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,85 @@
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.db.models import Q
from django.template.loader import get_template
from django.utils.html import strip_tags
from gregory.models import Articles, Trials, Team, Subject, MLPredictions
from sitesettings.models import *
from subscriptions.models import Subscribers
import datetime
import requests
from gregory.models import Articles, Trials, Team, MLPredictions
from sitesettings.models import CustomSetting
from subscriptions.management.commands.utils.send_email import send_email
from django.db.models import Prefetch


class Command(BaseCommand):
help = 'Sends an admin summary every 2 days.'
help = 'Sends an admin summary every 2 days.'

def handle(self, *args, **options):
site = Site.objects.get_current()
customsettings = CustomSetting.objects.get(site=site)

# Step 1: Fetch all teams
teams = Team.objects.all()

if not teams.exists():
self.stdout.write(self.style.WARNING("No teams found. Skipping admin summary."))
return

for team in teams:
members = team.members.all() # Assuming `members` is a related field
subjects = team.subjects.all()

if not members.exists():
self.stdout.write(self.style.WARNING(f"No members found in team {team.name}. Skipping."))
continue

if not subjects.exists():
self.stdout.write(self.style.WARNING(f"No subjects associated with team {team.name}. Skipping."))
continue

for subject in subjects:
# Step 2: Fetch unsent articles and trials for this team and subject
articles = Articles.objects.filter(subjects=subject).exclude(sent_to_teams=team).prefetch_related(
Prefetch('ml_predictions', queryset=MLPredictions.objects.select_related('subject'))
)
trials = Trials.objects.filter(subjects=subject).exclude(sent_to_teams=team)

if not articles.exists() and not trials.exists():
self.stdout.write(self.style.WARNING(f'No new articles or trials for team "{team.name}" and subject "{subject}". Skipping.'))
continue

for member in members:
self.stdout.write(self.style.SUCCESS(f"Sending admin summary to {member.email}."))

# Step 3: Prepare the summary context for the email
summary_context = {
"articles": articles,
"trials": trials,
"admin": member.email,
"title": customsettings.title,
"email_footer": customsettings.email_footer,
"site": site,
}

def handle(self, *args, **options):
customsettings = CustomSetting.objects.get(site=Site.objects.get_current().id)
site = Site.objects.get_current()
# Get Teams
teams = Team.objects.all()
for team in teams:
members = team.members
subjects = team.subjects.all()
for subject in subjects:
# fetch the articles and trials that were not sent to the team it will be something like the following but we need to find a better way to track if the article was sent to that user
articles = Articles.objects.filter(subjects=subject).exclude(sent_to_teams=team).prefetch_related(
Prefetch('ml_predictions', queryset=MLPredictions.objects.select_related('subject'))
)
trials = Trials.objects.filter(subjects=subject).exclude(sent_to_teams=team)
results = []
for member in members:
print(f"sending to {member.email}") # Fixed from admin.email to member.email
summary = {
"articles": articles,
"trials": trials,
"admin": member.email, # Fixed from admin.email to member.email
"title": customsettings.title,
"email_footer": customsettings.email_footer,
"site": site,
}
to = member.email # Fixed from admin.email to member.email
html = get_template('emails/admin_summary.html').render(summary)
text = strip_tags(html)
result = self.send_simple_message(to=to, subject=f'{subject} | Admin Summary', html=html, text=text)
results.append(result.status_code)
# carefull, this will not keep track of a single failed delivery of the email
if 200 in results:
for article in articles:
article.sent_to_teams.add(team)
for trial in trials:
trial.sent_to_teams.add(team)
# Render email content
html_content = get_template('emails/admin_summary.html').render(summary_context)
text_content = strip_tags(html_content)

def send_simple_message(self, to, bcc=None, subject=None, text=None, html=None,
sender=f'GregoryAI <gregory@{Site.objects.get_current().domain}>',
email_postmark_api_url=settings.EMAIL_POSTMARK_API_URL,
email_postmark_api=settings.EMAIL_POSTMARK_API):
print(f"data=sender: {sender}, to: {to}, bcc: {bcc}")
payload = {
"MessageStream": "broadcast",
"From": sender,
"To": to,
"Bcc": bcc,
"Subject": subject,
"TextBody": text,
"HtmlBody": html
}

payload = {k: v for k, v in payload.items() if v is not None}
# Step 4: Send email
result = send_email(
to=member.email,
subject=f'{subject} | Admin Summary',
html=html_content,
text=text_content,
site=site,
sender_name="GregoryAI"
)

status = requests.post(
email_postmark_api_url,
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"X-Postmark-Server-Token": email_postmark_api,
},
json=payload
)
print(status)
return status
# Step 5: Log email success/failure
if result.status_code == 200:
self.stdout.write(self.style.SUCCESS(f"Admin summary email sent to {member.email}."))
# Step 6: Mark articles and trials as sent to this team
for article in articles:
article.sent_to_teams.add(team)
for trial in trials:
trial.sent_to_teams.add(team)
else:
self.stdout.write(self.style.ERROR(f"Failed to send email to {member.email}. Status: {result.status_code}"))
212 changes: 97 additions & 115 deletions django/subscriptions/management/commands/send_trials_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,121 +2,103 @@
from django.template.loader import get_template
from django.utils.html import strip_tags
from django.contrib.sites.models import Site
from django.conf import settings
from gregory.models import Trials, Subject
from subscriptions.management.commands.utils.send_email import send_email
from subscriptions.management.commands.utils.subscription import get_trials_for_list
from sitesettings.models import CustomSetting
from subscriptions.models import Lists, Subscribers, SentTrialNotification
import requests
from django.utils.timezone import now
from datetime import timedelta
from subscriptions.models import Lists, Subscribers, SentTrialNotification, FailedNotification


class Command(BaseCommand):
help = 'Sends real-time notifications for new clinical trials to subscribers, filtered by subjects, without relying on a sent flag on Trials.'

def handle(self, *args, **options):
customsettings = CustomSetting.objects.get(site=Site.objects.get_current().id)
site = Site.objects.get_current()

# Step 1: Find all lists that have subjects.
subject_lists = Lists.objects.filter(subjects__isnull=False,weekly_digest=False).distinct()

if not subject_lists.exists():
self.stdout.write(self.style.WARNING('No lists found with subjects.'))
return

for lst in subject_lists:
# Step 2: Get the subjects for this list
list_subjects = lst.subjects.all()

# If no subjects for some reason (shouldn't happen due to the filter, but just in case)
if not list_subjects.exists():
self.stdout.write(self.style.WARNING(f'List "{lst.list_name}" has no subjects. Skipping.'))
continue

# Step 3: Gather trials for these subjects
# Filter trials within the last 30 days
list_trials = Trials.objects.filter(
subjects__in=list_subjects,
discovery_date__gte=now() - timedelta(days=30)
).distinct()

# Step 4: Find subscribers who are subscribed to this list
subscribers = Subscribers.objects.filter(
active=True,
subscriptions=lst
).distinct()

if not subscribers.exists():
self.stdout.write(self.style.WARNING(f'No subscribers found for the list "{lst.list_name}".'))
continue

# For each subscriber, send trials not yet sent
for subscriber in subscribers:
# Determine which have already been sent to this list for this subscriber
already_sent_ids = SentTrialNotification.objects.filter(
trial__in=list_trials,
list=lst,
subscriber=subscriber
).values_list('trial_id', flat=True)

# Filter out already sent trials
new_trials = list_trials.exclude(pk__in=already_sent_ids)

if new_trials.exists():
summary_context = {
"trials": new_trials,
"title": customsettings.title,
"email_footer": customsettings.email_footer,
"site": site,
}

html_content = get_template('emails/trial_notification.html').render(summary_context)
text_content = strip_tags(html_content)

result = self.send_simple_message(
to=subscriber.email,
subject='There are new clinical trials',
html=html_content,
text=text_content,
site=site,
customsettings=customsettings
)

if result.status_code == 200:
self.stdout.write(self.style.SUCCESS(f'Email sent to {subscriber.email} for list "{lst.list_name}".'))
# Record these trials as sent to this subscriber for this list
for trial in new_trials:
SentTrialNotification.objects.get_or_create(trial=trial, list=lst, subscriber=subscriber)
else:
self.stdout.write(self.style.ERROR(f'Failed to send email to {subscriber.email} for list "{lst.list_name}". Status: {result.status_code}'))
else:
self.stdout.write(self.style.WARNING(f'No new trials found for {subscriber.email} in list "{lst.list_name}".'))

def send_simple_message(self, to, subject, html, text, site, customsettings):
sender = 'GregoryAI <gregory@' + site.domain + '>'
email_postmark_api_url = settings.EMAIL_POSTMARK_API_URL
email_postmark_api = settings.EMAIL_POSTMARK_API

payload = {
"MessageStream": "broadcast",
"From": sender,
"To": to,
"Subject": subject,
"TextBody": text,
"HtmlBody": html
}

response = requests.post(
email_postmark_api_url,
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"X-Postmark-Server-Token": email_postmark_api,
},
json=payload
)

print("Status Code:", response.status_code)
print("Response:", response.json())

return response
help = 'Sends real-time notifications for new clinical trials to subscribers, filtered by subjects, without relying on a sent flag on Trials.'

def handle(self, *args, **options):
customsettings = CustomSetting.objects.get(site=Site.objects.get_current().id)
site = Site.objects.get_current()

# Step 1: Find all lists that have subjects but are not weekly digests
subject_lists = Lists.objects.filter(subjects__isnull=False, weekly_digest=False).distinct()

if not subject_lists.exists():
self.stdout.write(self.style.WARNING('No lists found with subjects.'))
return

for lst in subject_lists:
# Step 2: Use the shared utility function to fetch trials
list_trials = get_trials_for_list(lst)

if not list_trials.exists():
self.stdout.write(self.style.WARNING(f'No trials found for the list "{lst.list_name}". Skipping.'))
continue

# Step 3: Find active subscribers for the list
subscribers = Subscribers.objects.filter(
active=True,
subscriptions=lst
).distinct()

if not subscribers.exists():
self.stdout.write(self.style.WARNING(f'No subscribers found for the list "{lst.list_name}".'))
continue

# Step 4: Notify each subscriber of new trials
for subscriber in subscribers:
# Determine which trials have already been sent to this subscriber for this list
already_sent_ids = SentTrialNotification.objects.filter(
trial__in=list_trials,
list=lst,
subscriber=subscriber
).values_list('trial_id', flat=True)

# Filter out already sent trials
new_trials = list_trials.exclude(pk__in=already_sent_ids)

if not new_trials.exists():
self.stdout.write(self.style.WARNING(f'No new trials for {subscriber.email} in list "{lst.list_name}".'))
continue

# Step 5: Prepare and send the email
summary_context = {
"trials": new_trials,
"title": customsettings.title,
"email_footer": customsettings.email_footer,
"site": site,
}

html_content = get_template('emails/trial_notification.html').render(summary_context)
text_content = strip_tags(html_content)

result = send_email(
to=subscriber.email,
subject='There are new clinical trials',
html=html_content,
text=text_content,
site=site,
sender_name="GregoryAI"
)

# Step 6: Parse the Postmark response
if result.status_code == 200:
response_data = result.json()
error_code = response_data.get("ErrorCode", 0)
message = response_data.get("Message", "Unknown error")

if error_code == 0: # Successful delivery
self.stdout.write(self.style.SUCCESS(f"Email sent to {subscriber.email} for list '{lst.list_name}'."))
# Record sent notifications for the new trials
for trial in new_trials:
SentTrialNotification.objects.get_or_create(trial=trial, list=lst, subscriber=subscriber)
else: # Failed delivery
self.stdout.write(self.style.ERROR(f"Failed to send email to {subscriber.email} for list '{lst.list_name}'. Reason: {message}"))
FailedNotification.objects.create(
subscriber=subscriber,
list=lst,
reason=message
)
else:
# Log generic failure if response status is not 200
self.stdout.write(self.style.ERROR(f"Failed to send email to {subscriber.email} for list '{lst.list_name}'. Status: {result.status_code}"))
FailedNotification.objects.create(
subscriber=subscriber,
list=lst,
reason=f"HTTP Status {result.status_code}"
)
Loading

0 comments on commit be24a04

Please sign in to comment.