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 django.contrib import admin
|
||||||
|
|
||||||
from .models import Tool, Task, Reminder, Event
|
from .models import Tool, Task, Event, GroupTaskSubscription, GroupToolSubscription
|
||||||
|
|
||||||
admin.site.register(Tool)
|
admin.site.register(Tool)
|
||||||
|
|
||||||
@ -10,5 +10,6 @@ class TaskAdmin(admin.ModelAdmin):
|
|||||||
prepopulated_fields = {"slug": ("name",)}
|
prepopulated_fields = {"slug": ("name",)}
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Reminder)
|
admin.site.register(GroupTaskSubscription)
|
||||||
|
admin.site.register(GroupToolSubscription)
|
||||||
admin.site.register(Event)
|
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.core.management.base import BaseCommand, CommandError
|
||||||
from django.template import loader
|
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):
|
class Command(BaseCommand):
|
||||||
help = 'Sends any notifications for upcoming and overdue tasks'
|
help = 'Sends any notifications for upcoming and overdue tasks'
|
||||||
|
|
||||||
def _active_reminders(self):
|
def _active_task_subscriptions(self):
|
||||||
for reminder in Reminder.objects.all():
|
for subscription in GroupTaskSubscription.objects.all():
|
||||||
if reminder.should_remind:
|
if subscription.should_remind:
|
||||||
yield reminder
|
yield subscription
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
for tool_subscription in GroupToolSubscription.objects.all():
|
||||||
template = loader.get_template('tasks/notificationEmail.txt')
|
for subscription in tool_subscription.get_task_subscriptions():
|
||||||
|
if subscription.should_remind:
|
||||||
|
yield subscription
|
||||||
|
|
||||||
reminders_per_user = {
|
def _expand_group_subscriptions_to_users(self, subscriptions):
|
||||||
user: sorted(reminders, key=lambda r: r.task.next_recurrence)
|
out = {}
|
||||||
for user, reminders in groupby(self._active_reminders(), lambda r: r.user)
|
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:
|
if not user.email:
|
||||||
self.stdout.write(self.style.ERROR(
|
self.stdout.write(self.style.ERROR(
|
||||||
f"Can't send email, user '{user}' is missing an email address"))
|
f"Can't send email, user '{user}' is missing an email address"))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS(
|
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:
|
try:
|
||||||
send_mail(
|
send_mail(
|
||||||
subject=f'[CMS Tool Maintenance] {len(reminders)} tasks are upcoming or overdue!',
|
subject=f'[CMS Tool Maintenance] {len(subscriptions)} tasks are upcoming or overdue!',
|
||||||
message=template.render({'reminders': reminders}).strip(),
|
message=template.render({'subscriptions': subscriptions}).strip(),
|
||||||
from_email='adam@adamgoldsmith.name',
|
from_email='adam@adamgoldsmith.name',
|
||||||
recipient_list=[user.email],
|
recipient_list=[user.email],
|
||||||
fail_silently=False
|
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 dateutil.rrule import rrulestr
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -58,22 +59,53 @@ class Task(models.Model):
|
|||||||
return next_rec < datetime.now()
|
return next_rec < datetime.now()
|
||||||
|
|
||||||
|
|
||||||
class Reminder(models.Model):
|
class SubscriptionSettings(models.Model):
|
||||||
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
|
days_before = models.PositiveIntegerField()
|
||||||
task = models.ForeignKey(Task, on_delete=models.CASCADE)
|
|
||||||
days_before = models.IntegerField()
|
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:
|
class Meta:
|
||||||
# Django doesn't support multiple-column primary keys
|
# 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
|
@property
|
||||||
def should_remind(self):
|
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)
|
return self.task.is_overdue or (time_until_overdue.days <= self.days_before)
|
||||||
|
|
||||||
def __str__(self):
|
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):
|
class Event(models.Model):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
The following tasks are upcoming or overdue:
|
The following tasks are upcoming or overdue:
|
||||||
{% for reminder in reminders %}
|
{% for subscription in subscriptions %}
|
||||||
- {{ reminder.task.name }} {{ reminder.task.next_recurrence|date }}
|
- {{ subscription.task.name }} {{ subscription.task.next_recurrence|date }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
Loading…
Reference in New Issue
Block a user