Switch to Group-based reminder subscriptions, add tool subscriptions
This commit is contained in:
parent
365c97d0ef
commit
fb0b5be914
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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')},
|
||||
},
|
||||
),
|
||||
]
|
@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
@ -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):
|
||||
|
@ -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 %}
|
||||
|
Loading…
Reference in New Issue
Block a user