Switch to Group-based reminder subscriptions, add tool subscriptions

This commit is contained in:
Adam Goldsmith 2020-12-17 11:47:22 -05:00
parent 365c97d0ef
commit fb0b5be914
6 changed files with 113 additions and 50 deletions

View File

@ -1,6 +1,6 @@
from django.contrib import admin
from .models import Tool, Task, Reminder, Event
from .models import Tool, Task, Event, GroupTaskSubscription, GroupToolSubscription
admin.site.register(Tool)
@ -10,5 +10,6 @@ class TaskAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("name",)}
admin.site.register(Reminder)
admin.site.register(GroupTaskSubscription)
admin.site.register(GroupToolSubscription)
admin.site.register(Event)

View File

@ -4,38 +4,54 @@ from django.core.mail import send_mail
from django.core.management.base import BaseCommand, CommandError
from django.template import loader
from tasks.models import Tool, Task, Event, Reminder
from tasks.models import Tool, Task, Event, GroupToolSubscription, GroupTaskSubscription
class Command(BaseCommand):
help = 'Sends any notifications for upcoming and overdue tasks'
def _active_reminders(self):
for reminder in Reminder.objects.all():
if reminder.should_remind:
yield reminder
def _active_task_subscriptions(self):
for subscription in GroupTaskSubscription.objects.all():
if subscription.should_remind:
yield subscription
def handle(self, *args, **options):
template = loader.get_template('tasks/notificationEmail.txt')
for tool_subscription in GroupToolSubscription.objects.all():
for subscription in tool_subscription.get_task_subscriptions():
if subscription.should_remind:
yield subscription
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)
def _expand_group_subscriptions_to_users(self, subscriptions):
out = {}
for subscription in subscriptions:
for user in subscription.group.user_set.all():
if user not in out:
out[user] = []
out[user].append(subscription)
return {
user: sorted(subscriptions, key=lambda r: r.task.next_recurrence)
for user, subscriptions in out.items()
}
for user, reminders in reminders_per_user.items():
def handle(self, *args, **options):
template = loader.get_template('tasks/notificationEmail.txt.dtl')
group_subscriptions = self._active_task_subscriptions()
subscriptions_per_user = self._expand_group_subscriptions_to_users(group_subscriptions)
for user, subscriptions in subscriptions_per_user.items():
if not user.email:
self.stdout.write(self.style.ERROR(
f"Can't send email, user '{user}' is missing an email address"))
continue
self.stdout.write(self.style.SUCCESS(
f'Sending notification for {len(reminders)} task(s) to {user}'))
f'Sending notification for {len(subscriptions)} task(s) to {user}'))
try:
send_mail(
subject=f'[CMS Tool Maintenance] {len(reminders)} tasks are upcoming or overdue!',
message=template.render({'reminders': reminders}).strip(),
subject=f'[CMS Tool Maintenance] {len(subscriptions)} tasks are upcoming or overdue!',
message=template.render({'subscriptions': subscriptions}).strip(),
from_email='adam@adamgoldsmith.name',
recipient_list=[user.email],
fail_silently=False

View File

@ -0,0 +1,39 @@
# Generated by Django 3.1.4 on 2020-12-17 16:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('tasks', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='GroupToolSubscription',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('days_before', models.PositiveIntegerField()),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')),
('tool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.tool')),
],
options={
'unique_together': {('group', 'tool')},
},
),
migrations.CreateModel(
name='GroupTaskSubscription',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('days_before', models.PositiveIntegerField()),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.task')),
],
options={
'unique_together': {('group', 'task')},
},
),
]

View File

@ -1,25 +0,0 @@
# 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)),
],
),
]

View File

@ -2,6 +2,7 @@ from datetime import datetime
from dateutil.rrule import rrulestr
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.db import models
from django.urls import reverse
@ -58,22 +59,53 @@ 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 SubscriptionSettings(models.Model):
days_before = models.PositiveIntegerField()
class Meta:
abstract = True
def __str__(self):
return f"{self.days_before} day(s)"
class GroupToolSubscription(SubscriptionSettings):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
tool = models.ForeignKey(Tool, on_delete=models.CASCADE)
def get_task_subscriptions(self):
for task in self.tool.task_set.all():
yield GroupTaskSubscription(
days_before=self.days_before,
group=self.group,
task=task)
class Meta:
# Django doesn't support multiple-column primary keys
unique_together = (("user", "task"),)
unique_together = (("group", "tool"),)
def __str__(self):
return f"{self.group}-{self.tool}, {super().__str__()}"
class GroupTaskSubscription(SubscriptionSettings):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
task = models.ForeignKey(Task, on_delete=models.CASCADE)
class Meta:
# Django doesn't support multiple-column primary keys
unique_together = (("group", "task"),)
@property
def should_remind(self):
time_until_overdue = self.task.next_recurrence - datetime.now()
next_recurrence = self.task.next_recurrence
if next_recurrence is None:
return False
time_until_overdue = 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)"
return f"{self.group}-{self.task}, {super().__str__()}"
class Event(models.Model):

View File

@ -1,4 +1,4 @@
The following tasks are upcoming or overdue:
{% for reminder in reminders %}
- {{ reminder.task.name }} {{ reminder.task.next_recurrence|date }}
{% for subscription in subscriptions %}
- {{ subscription.task.name }} {{ subscription.task.next_recurrence|date }}
{% endfor %}