Skip to content

Commit

Permalink
Merge pull request #107 from fpdcc/feature/expiry-email
Browse files Browse the repository at this point in the history
Send license expiration notification emails
  • Loading branch information
xmedr authored Sep 6, 2023
2 parents 4b12313 + 149d7a2 commit 9aea97b
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# For local dev, copy this file as '.env' and paste the mandrill api key as the password
EMAIL_HOST_PASSWORD = 'super-secret'
EMAIL_HOST = 'smtp.mandrillapp.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = '[email protected]'

BASE_URL = 'localhost:8000'
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ services:
# Docker automatically recognizes your changes.
- .:/app
- ${PWD}/docsearch/local_settings.dev.py:/app/docsearch/local_settings.py
env_file:
- .env
command: python manage.py runserver 0.0.0.0:8000

migration:
Expand Down
4 changes: 4 additions & 0 deletions docsearch/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@

admin.site.site_header = 'Forest Preserve of Cook County Document Search Admin'
admin.site.site_title = 'Document Search'

class NotificationSubscriptionAdmin(admin.ModelAdmin):
list_display = ["user"]
admin.site.register(models.NotificationSubscription, NotificationSubscriptionAdmin)
15 changes: 15 additions & 0 deletions docsearch/local_settings.example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
import os
import socket

# SECURITY WARNING: keep the secret key used in production secret!
Expand Down Expand Up @@ -52,3 +53,17 @@
'ADMIN_URL': 'http://solr:8983/solr/admin/cores'
},
}

BASE_URL = os.getenv('BASE_URL', 'localhost:8000')

try:
EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
except KeyError:
print('Email password not found, email sending not turned on.')
else:
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.mandrillapp.com')
EMAIL_PORT = os.getenv('EMAIL_PORT', 587)
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '[email protected]')
EMAIL_USE_TLS = True

DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
119 changes: 119 additions & 0 deletions docsearch/management/commands/send_expiration_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from docsearch.models import License, NotificationSubscription
from docsearch.settings import BASE_URL
from django.core.management.base import BaseCommand
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
from django.db import transaction

import datetime
from dateutil.relativedelta import relativedelta
from csv import DictWriter
from io import StringIO


class Command(BaseCommand):
help = (
"Send notification emails for nearly expired licenses."
)

def generate_csv(self, licenses):
'''
Create a file in memory with details of licenses, intended to be written later
'''
file = StringIO()
field_names = ["license_number", "end_date", "url"]
header = ["License Number", "End Date", "Link"]
writer = DictWriter(file, fieldnames=field_names)

writer.writer.writerow(header)
for l in licenses:
writer.writerow(l)

return file

def attach_csv(self, email, near_expired, date):
'''
If any expiring licenses exist, attach a csv report to the email
'''
if len(near_expired) > 0:
attachment = self.generate_csv(near_expired)
email.attach('expiring_licenses_{}.csv'.format(str(date.year)), attachment.getvalue())

def get_near_expired_licenses(self, present, future_limit):
'''
Returns all licenses expiring between now and a future date
'''
dates_to_exclude = ['continuous', 'indefinite', 'perpetual', 'cancelled', 'TBD']
licenses = License.objects.exclude(end_date=None).exclude(end_date__in=dates_to_exclude)

result = []
for l in licenses:
# The format is YYYY-MM-DD
year, month, day = [int(time) for time in l.end_date.split("-")]

end_date = datetime.date(year, month, day)
if present <= end_date and end_date <= future_limit:
obj = {
"url": BASE_URL + l.get_absolute_url(),
"license_number": l.license_number,
"end_date": end_date
}

result.append(obj)

return result

def prep_email(self, subscribers, near_expired, date_range_start, date_range_end, subject):
'''
Assign recipients and build the contents of the email
'''
recipients = []
for sub in subscribers:
recipients.append(sub.user.email)
sub.notification_date = date_range_end
sub.save()

body = render_to_string(
'emails/license_expiration.html',
{
"n_licenses": str(len(near_expired)),
"date_range_start": date_range_start.strftime("%m/%d/%Y"),
"date_range_end": date_range_end.strftime("%m/%d/%Y"),
},
)

email = EmailMessage(
subject=subject,
body=body,
to=recipients,
)
email.content_subtype = 'html'

self.attach_csv(email, near_expired, date_range_start)

return email

@transaction.atomic
def handle(self, *args, **options):
today = datetime.date.today()

if NotificationSubscription.objects.filter(notification_date=today).exists():
self.stdout.write("Checking for licenses expiring soon...")

one_year_from_now = datetime.date.today() + relativedelta(years=1)
near_expired = self.get_near_expired_licenses(today, one_year_from_now)
self.stdout.write(f"{len(near_expired)} license(s) found")

subscribers = NotificationSubscription.objects.filter(notification_date=today)
subject = "Licenses expiring in the next 12 months"
email = self.prep_email(
subscribers=subscribers,
near_expired=near_expired,
date_range_start=today,
date_range_end=one_year_from_now,
subject=subject
)

self.stdout.write("Sending emails...")
email.send()
self.stdout.write(self.style.SUCCESS("Emails sent!"))
Loading

0 comments on commit 9aea97b

Please sign in to comment.