diff --git a/tasks/admin.py b/tasks/admin.py index 197063c..4d246f2 100644 --- a/tasks/admin.py +++ b/tasks/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Tool, Task, Event +from .models import Tool, Task, Reminder, Event admin.site.register(Tool) @@ -10,4 +10,5 @@ class TaskAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} +admin.site.register(Reminder) admin.site.register(Event) diff --git a/tasks/management/commands/sendNotifications.py b/tasks/management/commands/sendNotifications.py index 12e7177..69cf37c 100644 --- a/tasks/management/commands/sendNotifications.py +++ b/tasks/management/commands/sendNotifications.py @@ -1,20 +1,49 @@ import smtplib from email.message import EmailMessage +from itertools import groupby from django.core.management.base import BaseCommand, CommandError -from tasks.models import Tool, Task, Event +from tasks.models import Tool, Task, Event, Reminder class Command(BaseCommand): help = 'Sends any notifications for upcoming and overdue tasks' - # TODO: actually send notifications + def _send_email(self, to, subject, content): + msg = EmailMessage() + msg.set_content(content) + msg['Subject'] = subject + msg['From'] = 'adam@adamgoldsmith.name' + msg['To'] = to + + with smtplib.SMTP("localhost") as server: + server.send_message(msg) + + def _active_reminders(self): + for reminder in Reminder.objects.all(): + if reminder.should_remind: + yield reminder + + def _format_reminder_lines(self, reminders): + for reminder in reminders: + next_date = reminder.task.next_recurrence.strftime('%Y-%m-%d') + yield f" - {reminder.task.name} - {next_date}" + def handle(self, *args, **options): - for tool in Tool.objects.all(): - print(tool.name) - for task in tool.task_set.all(): - print('==>', task.name, 'next:', task.next_recurrence()) - if task.is_overdue(): - self.stdout.write(self.style.SUCCESS( - f'Sending Notification for task {task.name}')) + reminders_per_user = { + user: sorted(reminders, key=lambda r: r.task.next_recurrence) + for user, reminders in groupby(self._active_reminders(), lambda r: r.user) + } + + for user, reminders in reminders_per_user.items(): + self.stdout.write(self.style.SUCCESS( + f'Sending notification for {len(reminders)} task(s) to {user}')) + + contents = "The following tasks are upcoming or overdue:\n\n" + \ + ('\n'.join(self._format_reminder_lines(reminders))) + + self._send_email( + user.email, + f'[CMS Tool Maintenance] {len(reminders)} tasks are upcoming or overdue!', + contents) diff --git a/tasks/migrations/0002_reminder.py b/tasks/migrations/0002_reminder.py new file mode 100644 index 0000000..a33dc33 --- /dev/null +++ b/tasks/migrations/0002_reminder.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.4 on 2020-12-07 21:13 + +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), + ('tasks', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Subscription', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('days_before', models.IntegerField()), + ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.task')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/tasks/models.py b/tasks/models.py index dd64343..89ec706 100644 --- a/tasks/models.py +++ b/tasks/models.py @@ -58,6 +58,24 @@ class Task(models.Model): return next_rec < datetime.now() +class Reminder(models.Model): + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) + task = models.ForeignKey(Task, on_delete=models.CASCADE) + days_before = models.IntegerField() + + class Meta: + # Django doesn't support multiple-column primary keys + unique_together = (("user", "task"),) + + @property + def should_remind(self): + time_until_overdue = self.task.next_recurrence - datetime.now() + return self.task.is_overdue or (time_until_overdue.days <= self.days_before) + + def __str__(self): + return f"{self.user}-{self.task}, {self.days_before} day(s)" + + class Event(models.Model): task = models.ForeignKey(Task, on_delete=models.CASCADE) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)