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 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 passlib.pwd import genphrase
@ -126,7 +126,7 @@ def handle_register(form):
db.session.add(ver)
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")
return redirect(url_for("homepage.home"))
@ -168,7 +168,7 @@ def forgot_password():
db.session.add(ver)
db.session.commit()
sendVerifyEmail.delay(form.email.data, token)
send_verify_email.delay(form.email.data, token)
else:
send_anon_email.delay(email, "Unable to find account", """
<p>
@ -335,7 +335,7 @@ def unsubscribe_verify():
sub.token = randomString(32)
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")
return redirect(url_for("homepage.home"))

View File

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

View File

@ -348,10 +348,12 @@ class NotificationType(enum.Enum):
else:
return ""
def __str__(self):
return self.name
def __lt__(self, other):
return self.value < other.value
@classmethod
def choices(cls):
return [(choice, choice.getTitle()) for choice in cls]
@ -397,6 +399,10 @@ class Notification(db.Model):
prefs = self.user.notification_preferences
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):
id = db.Column(db.Integer, primary_key=True)

View File

@ -73,8 +73,12 @@ CELERYBEAT_SCHEDULE = {
'schedule': crontab(minute=10, hour=1),
},
'send_pending_notifications': {
'task': 'app.tasks.emails.sendPendingNotifications',
'task': 'app.tasks.emails.send_pending_notifications',
'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

View File

@ -18,7 +18,7 @@
from flask import render_template
from flask_mail import Message
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.utils import abs_url_for, abs_url, randomString
@ -36,7 +36,7 @@ def get_email_subscription(email):
@celery.task()
def sendVerifyEmail(email, token):
def send_verify_email(email, token):
sub = get_email_subscription(email)
if sub.blacklisted:
return
@ -59,7 +59,7 @@ def sendVerifyEmail(email, token):
@celery.task()
def sendUnsubscribeVerifyEmail(email):
def send_unsubscribe_verify(email):
sub = get_email_subscription(email)
if sub.blacklisted:
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.")
def sendNotificationEmail(notification):
def send_single_email(notification):
sub = get_email_subscription(notification.user.email)
if sub.blacklisted:
return
@ -125,11 +125,55 @@ def sendNotificationEmail(notification):
mail.send(msg)
@celery.task()
def sendPendingNotifications():
for notification in Notification.query.filter_by(emailed=False).all():
if notification.can_send_email():
sendNotificationEmail(notification)
def send_notification_digest(notifications: [Notification]):
user = notifications[0].user
sub = get_email_subscription(user.email)
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()
@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>
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>
<table class="table">