Notification emails #80

Merged
Oleg-Komarov merged 31 commits from notifications into main 2024-04-18 16:11:20 +02:00
3 changed files with 17 additions and 10 deletions
Showing only changes of commit eccfbd3baa - Show all commits

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.11 on 2024-04-12 18:14 # Generated by Django 4.2.11 on 2024-04-16 15:56
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -10,8 +10,8 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('actstream', '0003_add_follow_flag'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('actstream', '0003_add_follow_flag'),
] ]
operations = [ operations = [
@ -21,10 +21,12 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('action', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='actstream.action')), ('action', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='actstream.action')),
('sent', models.BooleanField(default=False)), ('email_sent', models.BooleanField(default=False)),
('processed_by_mailer_at', models.DateTimeField(default=None, null=True)), ('processed_by_mailer_at', models.DateTimeField(default=None, null=True)),
('read_at', models.DateTimeField(default=None, null=True)),
], ],
options={ options={
'indexes': [models.Index(fields=['processed_by_mailer_at'], name='notificatio_process_fc95bc_idx'), models.Index(fields=['recipient', 'read_at'], name='notificatio_recipie_564b1f_idx')],
'unique_together': {('recipient', 'action')}, 'unique_together': {('recipient', 'action')},
}, },
), ),

View File

@ -10,19 +10,24 @@ User = get_user_model()
class Notification(models.Model): class Notification(models.Model):
"""Notification records are created in Action's post_save signal. """Notification records are created in Action's post_save signal.
When a user marks a notification as read, a record is deleted. When a user marks a notification as read, read_at is set.
While a notification exists it can be picked up by a send_notifications management command send_notification_emails management command runs periodically in background and sends all
that runs periodically in background. notifications that haven't been processed yet, read_at is not checked when sending emails.
If a recipient opted out from receiving notifications of particular type (based on action.verb), email_sent flag is used only to record the fact that we attempted to send an email.
we shouldn't include it in the email, leaving sent=False. A user can unsubscribe from notification emails in their profile settings.
""" """
recipient = models.ForeignKey(User, null=False, on_delete=models.CASCADE) recipient = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
action = models.ForeignKey(Action, null=False, on_delete=models.CASCADE) action = models.ForeignKey(Action, null=False, on_delete=models.CASCADE)
sent = models.BooleanField(default=False, null=False) email_sent = models.BooleanField(default=False, null=False)
processed_by_mailer_at = models.DateTimeField(default=None, null=True) processed_by_mailer_at = models.DateTimeField(default=None, null=True)
read_at = models.DateTimeField(default=None, null=True)
class Meta: class Meta:
indexes = [
models.Index(fields=['processed_by_mailer_at']),
models.Index(fields=['recipient', 'read_at']),
]
unique_together = ['recipient', 'action'] unique_together = ['recipient', 'action']
def format_email_txt(self): def format_email_txt(self):

View File

@ -28,7 +28,7 @@ class Command(BaseCommand):
logger.info(f'{recipient} has unconfirmed email, skipping') logger.info(f'{recipient} has unconfirmed email, skipping')
n.save() n.save()
continue continue
n.sent = True n.email_sent = True
# first mark as processed, then send: avoid spamming in case of a crash-loop # first mark as processed, then send: avoid spamming in case of a crash-loop
n.save() n.save()
logger.info(f'sending an email to {recipient}: {n.action}') logger.info(f'sending an email to {recipient}: {n.action}')