Add daily notification digests

This commit is contained in:
rubenwardy 2020-12-06 15:02:02 +00:00
parent 35e1659b77
commit 3aa12be544
7 changed files with 111 additions and 19 deletions

View File

@ -23,7 +23,7 @@ from wtforms import *
from wtforms.validators import * from wtforms.validators import *
from app.models import * from app.models import *
from app.tasks.emails import sendVerifyEmail, send_anon_email, sendUnsubscribeVerifyEmail, send_user_email from app.tasks.emails import send_verify_email, send_anon_email, send_unsubscribe_verify, send_user_email
from app.utils import randomString, make_flask_login_password, is_safe_url, check_password_hash, addAuditLog from app.utils import randomString, make_flask_login_password, is_safe_url, check_password_hash, addAuditLog
from passlib.pwd import genphrase from passlib.pwd import genphrase
@ -126,7 +126,7 @@ def handle_register(form):
db.session.add(ver) db.session.add(ver)
db.session.commit() db.session.commit()
sendVerifyEmail.delay(form.email.data, token) send_verify_email.delay(form.email.data, token)
flash("Check your email address to verify your account", "success") flash("Check your email address to verify your account", "success")
return redirect(url_for("homepage.home")) return redirect(url_for("homepage.home"))
@ -168,7 +168,7 @@ def forgot_password():
db.session.add(ver) db.session.add(ver)
db.session.commit() db.session.commit()
sendVerifyEmail.delay(form.email.data, token) send_verify_email.delay(form.email.data, token)
else: else:
send_anon_email.delay(email, "Unable to find account", """ send_anon_email.delay(email, "Unable to find account", """
<p> <p>
@ -335,7 +335,7 @@ def unsubscribe_verify():
sub.token = randomString(32) sub.token = randomString(32)
db.session.commit() db.session.commit()
sendUnsubscribeVerifyEmail.delay(form.email.data) send_unsubscribe_verify.delay(form.email.data)
flash("Check your email address to continue the unsubscribe", "success") flash("Check your email address to continue the unsubscribe", "success")
return redirect(url_for("homepage.home")) return redirect(url_for("homepage.home"))

View File

@ -6,7 +6,7 @@ from wtforms.validators import *
from app.models import * from app.models import *
from app.utils import nonEmptyOrNone, addAuditLog, randomString from app.utils import nonEmptyOrNone, addAuditLog, randomString
from app.tasks.emails import sendVerifyEmail from app.tasks.emails import send_verify_email
from . import bp from . import bp
@ -141,7 +141,7 @@ def handle_email_notifications(user, prefs: UserNotificationPreferences, is_new,
flash("Check your email to confirm it", "success") flash("Check your email to confirm it", "success")
sendVerifyEmail.delay(newEmail, token) send_verify_email.delay(newEmail, token)
return redirect(url_for("homepage.home")) return redirect(url_for("homepage.home"))
db.session.commit() db.session.commit()

View File

@ -348,10 +348,12 @@ class NotificationType(enum.Enum):
else: else:
return "" return ""
def __str__(self): def __str__(self):
return self.name return self.name
def __lt__(self, other):
return self.value < other.value
@classmethod @classmethod
def choices(cls): def choices(cls):
return [(choice, choice.getTitle()) for choice in cls] return [(choice, choice.getTitle()) for choice in cls]
@ -397,6 +399,10 @@ class Notification(db.Model):
prefs = self.user.notification_preferences prefs = self.user.notification_preferences
return prefs and self.user.email and prefs.get_can_email(self.type) return prefs and self.user.email and prefs.get_can_email(self.type)
def can_send_digest(self):
prefs = self.user.notification_preferences
return prefs and self.user.email and prefs.get_can_digest(self.type)
class UserNotificationPreferences(db.Model): class UserNotificationPreferences(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)

View File

@ -73,8 +73,12 @@ CELERYBEAT_SCHEDULE = {
'schedule': crontab(minute=10, hour=1), 'schedule': crontab(minute=10, hour=1),
}, },
'send_pending_notifications': { 'send_pending_notifications': {
'task': 'app.tasks.emails.sendPendingNotifications', 'task': 'app.tasks.emails.send_pending_notifications',
'schedule': crontab(minute='*/5'), 'schedule': crontab(minute='*/5'),
},
'send_notification_digests': {
'task': 'app.tasks.emails.send_pending_digests',
'schedule': crontab(minute=0, hour=14),
} }
} }
celery.conf.beat_schedule = CELERYBEAT_SCHEDULE celery.conf.beat_schedule = CELERYBEAT_SCHEDULE

View File

@ -18,7 +18,7 @@
from flask import render_template from flask import render_template
from flask_mail import Message from flask_mail import Message
from app import mail from app import mail
from app.models import Notification, db, EmailSubscription from app.models import Notification, db, EmailSubscription, User
from app.tasks import celery from app.tasks import celery
from app.utils import abs_url_for, abs_url, randomString from app.utils import abs_url_for, abs_url, randomString
@ -36,7 +36,7 @@ def get_email_subscription(email):
@celery.task() @celery.task()
def sendVerifyEmail(email, token): def send_verify_email(email, token):
sub = get_email_subscription(email) sub = get_email_subscription(email)
if sub.blacklisted: if sub.blacklisted:
return return
@ -59,7 +59,7 @@ def sendVerifyEmail(email, token):
@celery.task() @celery.task()
def sendUnsubscribeVerifyEmail(email): def send_unsubscribe_verify(email):
sub = get_email_subscription(email) sub = get_email_subscription(email)
if sub.blacklisted: if sub.blacklisted:
return return
@ -103,7 +103,7 @@ def send_anon_email(email: str, subject: str, text: str, html=None):
"You are receiving this email because someone (hopefully you) entered your email address as a user's email.") "You are receiving this email because someone (hopefully you) entered your email address as a user's email.")
def sendNotificationEmail(notification): def send_single_email(notification):
sub = get_email_subscription(notification.user.email) sub = get_email_subscription(notification.user.email)
if sub.blacklisted: if sub.blacklisted:
return return
@ -125,11 +125,55 @@ def sendNotificationEmail(notification):
mail.send(msg) mail.send(msg)
@celery.task() def send_notification_digest(notifications: [Notification]):
def sendPendingNotifications(): user = notifications[0].user
for notification in Notification.query.filter_by(emailed=False).all():
if notification.can_send_email(): sub = get_email_subscription(user.email)
sendNotificationEmail(notification) if sub.blacklisted:
return
msg = Message("{} new notifications".format(len(notifications)), recipients=[user.email])
msg.body = "".join(["<{}> {}\nView: {}\n\n".format(notification.causer.display_name, notification.title, abs_url(notification.url)) for notification in notifications])
msg.body += "Manage email settings: {}\nUnsubscribe: {}".format(
abs_url_for("users.email_notifications", username=user.username),
abs_url_for("users.unsubscribe", token=sub.token))
msg.html = render_template("emails/notification_digest.html", notifications=notifications, user=user, sub=sub)
mail.send(msg)
@celery.task()
def send_pending_digests():
for user in User.query.filter(User.notifications.any(emailed=False)).all():
to_send = []
for notification in user.notifications:
if not notification.emailed and notification.can_send_digest():
to_send.append(notification)
notification.emailed = True
if len(to_send) > 0:
send_notification_digest(to_send)
notification.emailed = True
db.session.commit() db.session.commit()
@celery.task()
def send_pending_notifications():
for user in User.query.filter(User.notifications.any(emailed=False)).all():
to_send = []
for notification in user.notifications:
if not notification.emailed:
if notification.can_send_email():
to_send.append(notification)
notification.emailed = True
elif not notification.can_send_digest():
notification.emailed = True
db.session.commit()
if len(to_send) > 1:
send_notification_digest(to_send)
elif len(to_send) > 0:
send_single_email(to_send[0])

View File

@ -0,0 +1,39 @@
{% extends "emails/base.html" %}
{% block content %}
{% for type, group in notifications | groupby("package.title") %}
<h2>
{{ type or _("Other Notifications") }}
</h2>
<ul>
{% for notification in group %}
<li>
<a href="{{ notification.url | abs_url }}">{{ notification.title }}</a> -
{{ _("from %(username)s.", username=notification.causer.display_name) }}
</li>
{% endfor %}
</ul>
{% endfor %}
<p style="margin-top: 3em;">
<a class="btn" href="{{ abs_url_for('notifications.list_all') }}">
View Notifications
</a>
</p>
{% endblock %}
{% block footer %}
You are receiving this email because you are a registered user of ContentDB,
and have email notifications enabled. <br>
<a href="{{ abs_url_for('users.email_notifications', username=user.username) }}">
{{ _("Manage your preferences") }}
</a>
|
<a href="{{ abs_url_for('users.unsubscribe', token=sub.token) }}">
{{ _("Unsubscribe") }}
</a> <br>
{% endblock %}

View File

@ -32,7 +32,6 @@
<p> <p>
Configure whether certain types of notifications are sent immediately, or as part of a daily digest. <br> Configure whether certain types of notifications are sent immediately, or as part of a daily digest. <br>
<i>Note: daily digests aren't implemented yet.</i>
</p> </p>
<table class="table"> <table class="table">