From dbbe5ec9e19f12a6ae67aa87c2fa09f7ffae832b Mon Sep 17 00:00:00 2001 From: Michael Hall Date: Sun, 13 May 2018 21:30:43 -0400 Subject: [PATCH] Add EmailRecord to accounts to track what emails are being sent out. Only visible to site sysadmins --- accounts/admin.py | 31 +++++++++++++++- accounts/migrations/0004_add_email_record.py | 29 +++++++++++++++ accounts/models.py | 21 +++++++++++ .../commands/create_next_in_series.py | 11 +++++- .../commands/send_daily_attendee_update.py | 19 ++++++++-- .../commands/send_daily_member_update.py | 19 ++++++++-- .../send_email_confirmation_reminder.py | 14 ++++++- .../commands/send_event_reminder.py | 12 +++++- get_together/views/events.py | 37 +++++++++++++++---- get_together/views/new_user.py | 12 +++++- get_together/views/teams.py | 23 +++++++++++- 11 files changed, 204 insertions(+), 24 deletions(-) create mode 100644 accounts/migrations/0004_add_email_record.py diff --git a/accounts/admin.py b/accounts/admin.py index aa47805..ca76cdd 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.utils.safestring import mark_safe -from .models import Account, Badge, BadgeGrant, EmailConfirmation +from .models import Account, Badge, BadgeGrant, EmailConfirmation, EmailRecord # Register your models here. class AccountAdmin(admin.ModelAdmin): @@ -27,3 +27,32 @@ class ConfirmationAdmin(admin.ModelAdmin): list_display = ('email', 'user', 'key', 'expires') admin.site.register(EmailConfirmation, ConfirmationAdmin) + +class EmailAdmin(admin.ModelAdmin): + list_display = ['when', 'recipient_display', 'subject', 'sender_display', 'ok',] + list_filter = ['ok', 'when', 'sender'] + readonly_fields = ['when', 'email', 'subject', 'body', 'ok'] + search_fields = ['subject', 'body', 'to'] + + def sender_display(self, record): + if record.sender is not None: + return record.sender + else: + return 'System' + sender_display.short_description = 'From' + + def recipient_display(self, record): + if record.recipient is not None: + return '%s <%s>' % (record.recipient, record.email) + else: + return record.email + recipient_display.short_description = 'To' + + def has_delete_permission(self, request, obj=None): + return False + + def has_add_permission(self, request): + return False + + +admin.site.register(EmailRecord, EmailAdmin) \ No newline at end of file diff --git a/accounts/migrations/0004_add_email_record.py b/accounts/migrations/0004_add_email_record.py new file mode 100644 index 0000000..0d4db6c --- /dev/null +++ b/accounts/migrations/0004_add_email_record.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0 on 2018-05-13 23:43 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('accounts', '0003_auto_20180304_1649'), + ] + + operations = [ + migrations.CreateModel( + name='EmailRecord', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('when', models.DateTimeField(auto_now_add=True)), + ('email', models.EmailField(max_length=254)), + ('subject', models.CharField(max_length=128)), + ('body', models.TextField(max_length=1024)), + ('ok', models.BooleanField(default=True)), + ('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recv_messages', to=settings.AUTH_USER_MODEL)), + ('sender', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent_messages', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 5476e09..17eded6 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -107,3 +107,24 @@ class BadgeGrant(models.Model): return '%s for %s' % (self.badge.name, self.account.acctname) +class EmailRecord(models.Model): + """ + Model to store all the outgoing emails. + """ + when = models.DateTimeField( + null=False, auto_now_add=True + ) + sender = models.ForeignKey(User, related_name='sent_messages', null=True, blank=True, on_delete=models.SET_NULL) + recipient = models.ForeignKey(User, related_name='recv_messages', null=True, blank=True, on_delete=models.SET_NULL) + email = models.EmailField( + null=False, blank=False, + ) + subject = models.CharField( + null=False, max_length=128, + ) + body = models.TextField( + null=False, max_length=1024, + ) + ok = models.BooleanField( + null=False, default=True, + ) diff --git a/get_together/management/commands/create_next_in_series.py b/get_together/management/commands/create_next_in_series.py index 6ccff17..a67d041 100644 --- a/get_together/management/commands/create_next_in_series.py +++ b/get_together/management/commands/create_next_in_series.py @@ -6,6 +6,7 @@ from django.core.mail import send_mail from django.template.loader import get_template, render_to_string from events.models import Event, EventSeries, Attendee +from accounts.models import EmailRecord import time import datetime @@ -34,11 +35,19 @@ def email_host_new_event(event): email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') for attendee in Attendee.objects.filter(event=event, role=Attendee.HOST, user__user__account__is_email_confirmed=True): - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, recipient_list=[attendee.user.user.email], subject=email_subject, fail_silently=True, + ) + EmailRecord.objects.create( + sender=None, + recipient=attendee.user.user, + email=attendee.user.user.email, + subject=email_subject, + body=email_body_text, + ok=success ) \ No newline at end of file diff --git a/get_together/management/commands/send_daily_attendee_update.py b/get_together/management/commands/send_daily_attendee_update.py index 9b5d0bc..e676b6b 100644 --- a/get_together/management/commands/send_daily_attendee_update.py +++ b/get_together/management/commands/send_daily_attendee_update.py @@ -7,6 +7,7 @@ from django.contrib.sites.models import Site from django.utils import timezone from events.models import Event, Attendee +from accounts.models import EmailRecord import datetime @@ -35,8 +36,8 @@ class Command(BaseCommand): def send_new_attendees(event, new_attendees): if len(new_attendees) < 1: return - host_emails = [attendee.user.user.email for attendee in Attendee.objects.filter(event=event, role=Attendee.HOST) if attendee.user.user.account.is_email_confirmed] - if len(host_emails) < 1: + hosts = [attendee.user.user for attendee in Attendee.objects.filter(event=event, role=Attendee.HOST) if attendee.user.user.account.is_email_confirmed] + if len(hosts) < 1: return context = { 'event': event, @@ -47,13 +48,23 @@ def send_new_attendees(event, new_attendees): email_subject = '[GetTogether] New event attendees' email_body_text = render_to_string('get_together/emails/new_event_attendees.txt', context) email_body_html = render_to_string('get_together/emails/new_event_attendees.html', context) - email_recipients = host_emails + email_recipients = [host.email for host in hosts] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, recipient_list=email_recipients, subject=email_subject, ) + + for host in hosts: + EmailRecord.objects.create( + sender=None, + recipient=host, + email=host.email, + subject=email_subject, + body=email_body_text, + ok=success + ) \ No newline at end of file diff --git a/get_together/management/commands/send_daily_member_update.py b/get_together/management/commands/send_daily_member_update.py index 07c75b2..4cb9700 100644 --- a/get_together/management/commands/send_daily_member_update.py +++ b/get_together/management/commands/send_daily_member_update.py @@ -7,6 +7,7 @@ from django.contrib.sites.models import Site from django.utils import timezone from events.models import Event, Member +from accounts.models import EmailRecord import datetime @@ -35,8 +36,8 @@ class Command(BaseCommand): def send_new_members(team, new_members): if len(new_members) < 1: return - admin_emails = [member.user.user.email for member in Member.objects.filter(team=team, role=Member.ADMIN) if member.user.user.account.is_email_confirmed] - if len(admin_emails) < 1: + admins = [member.user.user for member in Member.objects.filter(team=team, role=Member.ADMIN) if member.user.user.account.is_email_confirmed] + if len(admins) < 1: return context = { 'team': team, @@ -47,13 +48,23 @@ def send_new_members(team, new_members): email_subject = '[GetTogether] New team members' email_body_text = render_to_string('get_together/emails/new_team_members.txt', context) email_body_html = render_to_string('get_together/emails/new_team_members.html', context) - email_recipients = admin_emails + email_recipients = [admin.email for admin in admins] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, recipient_list=email_recipients, subject=email_subject, ) + + for admin in admins: + EmailRecord.objects.create( + sender=None, + recipient=admin, + email=admin.email, + subject=email_subject, + body=email_body_text, + ok=success + ) \ No newline at end of file diff --git a/get_together/management/commands/send_email_confirmation_reminder.py b/get_together/management/commands/send_email_confirmation_reminder.py index aea3050..e1e1e90 100644 --- a/get_together/management/commands/send_email_confirmation_reminder.py +++ b/get_together/management/commands/send_email_confirmation_reminder.py @@ -5,7 +5,7 @@ from django.template.loader import get_template, render_to_string from django.conf import settings from django.contrib.sites.models import Site -from accounts.models import Account, EmailConfirmation +from accounts.models import Account, EmailConfirmation, EmailRecord import time import urllib @@ -47,13 +47,23 @@ class Command(BaseCommand): email_body_html = render_to_string('get_together/emails/confirm_email.html', context) email_recipients = [account.user.email] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( subject=email_subject, message=email_body_text, from_email=email_from, recipient_list=email_recipients, html_message=email_body_html ) + + EmailRecord.objects.create( + sender=None, + recipient=account.user, + email=account.user.email, + subject=email_subject, + body=email_body_text, + ok=success + ) time.sleep(0.1) + diff --git a/get_together/management/commands/send_event_reminder.py b/get_together/management/commands/send_event_reminder.py index 0a4c2c7..254da15 100644 --- a/get_together/management/commands/send_event_reminder.py +++ b/get_together/management/commands/send_event_reminder.py @@ -7,7 +7,7 @@ from django.contrib.sites.models import Site from django.utils import timezone from django.db.models import Q -from accounts.models import Account, EmailConfirmation +from accounts.models import EmailRecord from events.models import Event, Attendee import time @@ -47,7 +47,7 @@ class Command(BaseCommand): email_recipients = [attendee.user.user.email] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, @@ -58,4 +58,12 @@ class Command(BaseCommand): attendee.last_reminded = timezone.now() attendee.save() + EmailRecord.objects.create( + sender=None, + recipient=attendee.user.user, + email=attendee.user.user.email, + subject=email_subject, + body=email_body_text, + ok=success + ) time.sleep(0.1) diff --git a/get_together/views/events.py b/get_together/views/events.py index afdbccb..8587036 100644 --- a/get_together/views/events.py +++ b/get_together/views/events.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib import messages -from django.contrib.auth import logout as logout_user +from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.shortcuts import render, redirect, reverse, get_object_or_404 from django.contrib.sites.models import Site @@ -38,6 +38,8 @@ from events.forms import ( ) from events import location +from accounts.models import EmailRecord + import datetime import simplejson @@ -159,7 +161,7 @@ def invite_attendees(request, event_id): if email_form.is_valid(): to = email_form.cleaned_data['emails'] for email in to: - invite_attendee(email, event, request.user.profile) + invite_attendee(email, event, request.user) messages.add_message(request, messages.SUCCESS, message=_('Sent %s invites' % len(to))) return redirect(event.get_absolute_url()) team_form = EventInviteMemberForm() @@ -175,7 +177,7 @@ def invite_attendees(request, event_id): attendee = Attendee.objects.get(event=event, user=user) except: # No attendee record found, so send the invite - invite_attendee(user.user.email, event, request.user.profile) + invite_attendee(user.user, event, request.user) messages.add_message(request, messages.SUCCESS, message=_('Sent %s invites' % len(member_choices))) return redirect(event.get_absolute_url()) else: @@ -184,7 +186,7 @@ def invite_attendees(request, event_id): attendee = Attendee.objects.get(event=event, user=member.user) except: # No attendee record found, so send the invite - invite_attendee(member.user.user.email, event, request.user.profile) + invite_attendee(member.user.user, event, request.user) messages.add_message(request, messages.SUCCESS, message=_('Invited %s' % member.user)) return redirect(event.get_absolute_url()) email_form = EventInviteEmailForm() @@ -206,18 +208,23 @@ def invite_attendees(request, event_id): def invite_attendee(email, event, sender): context = { - 'sender': sender, + 'sender': sender.profile, 'team': event.team, 'event': event, 'site': Site.objects.get(id=1), } + recipient = None + if type(email) == User: + recipient = email + email = recipient.email + email_subject = '[GetTogether] Invite to attend %s' % event.name email_body_text = render_to_string('get_together/emails/attendee_invite.txt', context) email_body_html = render_to_string('get_together/emails/attendee_invite.html', context) email_recipients = [email] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, @@ -225,6 +232,14 @@ def invite_attendee(email, event, sender): subject=email_subject, fail_silently=True, ) + EmailRecord.objects.create( + sender=sender, + recipient=recipient, + email=email, + subject=email_subject, + body=email_body_text, + ok=success + ) def attend_event(request, event_id): @@ -278,7 +293,7 @@ def send_comment_emails(comment): email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') for attendee in comment.event.attendees.filter(user__account__is_email_confirmed=True): - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, @@ -286,6 +301,14 @@ def send_comment_emails(comment): subject=email_subject, fail_silently=True, ) + EmailRecord.objects.create( + sender=comment.author.user, + recipient=attendee.user.user, + email=attendee.user.user.email, + subject=email_subject, + body=email_body_text, + ok=success + ) @login_required def add_event_photo(request, event_id): diff --git a/get_together/views/new_user.py b/get_together/views/new_user.py index eaeb810..03ca6b3 100644 --- a/get_together/views/new_user.py +++ b/get_together/views/new_user.py @@ -13,6 +13,8 @@ from events.models.profiles import Team, UserProfile, Member, Category from events.models.events import Event, Place, Attendee from events.forms import SendNotificationsForm, UserForm, ConfirmProfileForm +from accounts.models import EmailRecord + from .utils import get_nearby_teams import datetime @@ -146,13 +148,21 @@ def user_send_confirmation_email(request): email_body_html = render_to_string('get_together/emails/confirm_email.html', context, request) email_recipients = [request.user.email] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( subject=email_subject, message=email_body_text, from_email=email_from, recipient_list=email_recipients, html_message=email_body_html ) + EmailRecord.objects.create( + sender=request.user, + recipient=request.user, + email=request.user.email, + subject=email_subject, + body=email_body_text, + ok=success + ) return render(request, 'get_together/new_user/sent_email_confirmation.html', context) @login_required diff --git a/get_together/views/teams.py b/get_together/views/teams.py index 7e7f4c3..4f0217b 100644 --- a/get_together/views/teams.py +++ b/get_together/views/teams.py @@ -16,6 +16,8 @@ from events.models.events import Event, CommonEvent, Place, Attendee from events.forms import TeamForm, NewTeamForm, DeleteTeamForm, TeamContactForm, TeamInviteForm from events import location +from accounts.models import EmailRecord + import datetime import simplejson @@ -242,7 +244,7 @@ def invite_member(email, team, sender): email_recipients = [email] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, @@ -251,6 +253,15 @@ def invite_member(email, team, sender): fail_silently=True, ) + EmailRecord.objects.create( + sender=sender.user, + recipient=None, + email=email, + subject=email_subject, + body=email_body_text, + ok=success + ) + def contact_member(member, body, sender): context = { @@ -265,7 +276,7 @@ def contact_member(member, body, sender): email_recipients = [member.user.user.email] email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community') - send_mail( + success = send_mail( from_email=email_from, html_message=email_body_html, message=email_body_text, @@ -273,6 +284,14 @@ def contact_member(member, body, sender): subject=email_subject, fail_silently=True, ) + EmailRecord.objects.create( + sender=sender.user, + recipient=member.user.user, + email=member.user.user.email, + subject=email_subject, + body=email_body_text, + ok=success + ) def show_org(request, org_slug): org = get_object_or_404(Organization, slug=org_slug)