Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send license expiration notification emails #107

Merged
merged 11 commits into from
Sep 6, 2023
Merged
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