Add notification settings

This commit is contained in:
rubenwardy 2020-12-05 05:24:27 +00:00
parent d32bb30071
commit c8e93a9f52
5 changed files with 191 additions and 4 deletions

@ -17,18 +17,65 @@
from flask import Blueprint, render_template, redirect, url_for from flask import Blueprint, render_template, redirect, url_for
from flask_login import current_user, login_required from flask_login import current_user, login_required
from app.models import db, Notification from flask_wtf import FlaskForm
from wtforms import BooleanField, SubmitField
from app.blueprints.users.profile import get_setting_tabs
from app.models import db, Notification, UserNotificationPreferences, NotificationType
bp = Blueprint("notifications", __name__) bp = Blueprint("notifications", __name__)
@bp.route("/notifications/") @bp.route("/notifications/")
@login_required @login_required
def list_all(): def list_all():
return render_template("notifications/list.html") return render_template("notifications/list.html")
@bp.route("/notifications/clear/", methods=["POST"]) @bp.route("/notifications/clear/", methods=["POST"])
@login_required @login_required
def clear(): def clear():
Notification.query.filter_by(user=current_user).delete() Notification.query.filter_by(user=current_user).delete()
db.session.commit() db.session.commit()
return redirect(url_for("notifications.list_all")) return redirect(url_for("notifications.list_all"))
@bp.route("/notifications/settings/", methods=["GET", "POST"])
@login_required
def settings():
is_new = False
prefs = current_user.notification_preferences
if prefs is None:
is_new = True
prefs = UserNotificationPreferences(current_user)
attrs = {
"submit": SubmitField("Save")
}
data = {}
types = []
for notificationType in NotificationType:
key = "pref_" + notificationType.toName()
types.append(notificationType)
attrs[key] = BooleanField("")
data[key] = getattr(prefs, key) == 2
SettingsForm = type("SettingsForm", (FlaskForm,), attrs)
form = SettingsForm(data=data)
if form.validate_on_submit():
for notificationType in NotificationType:
key = "pref_" + notificationType.toName()
field = getattr(form, key)
value = 2 if field.data else 0
setattr(prefs, key, value)
if is_new:
db.session.add(prefs)
db.session.commit()
return redirect(url_for("notifications.settings"))
return render_template("notifications/settings.html",
form=form, user=current_user, types=types,
tabs=get_setting_tabs(current_user), current_tab="notifications")

@ -83,6 +83,11 @@ def get_setting_tabs(user):
"title": "Edit Profile", "title": "Edit Profile",
"url": url_for("users.profile_edit", username=user.username) "url": url_for("users.profile_edit", username=user.username)
}, },
{
"id": "notifications",
"title": "Emails and Notifications",
"url": url_for("notifications.settings")
},
{ {
"id": "api_tokens", "id": "api_tokens",
"title": "API Tokens", "title": "API Tokens",

@ -166,6 +166,8 @@ class User(db.Model, UserMixin):
notifications = db.relationship("Notification", primaryjoin="User.id==Notification.user_id", notifications = db.relationship("Notification", primaryjoin="User.id==Notification.user_id",
order_by="Notification.created_at") order_by="Notification.created_at")
notification_preferences = db.relationship("UserNotificationPreferences", uselist=False, back_populates="user")
packages = db.relationship("Package", backref=db.backref("author", lazy="joined"), lazy="dynamic") packages = db.relationship("Package", backref=db.backref("author", lazy="joined"), lazy="dynamic")
requests = db.relationship("EditRequest", backref="author", lazy="dynamic") requests = db.relationship("EditRequest", backref="author", lazy="dynamic")
threads = db.relationship("Thread", backref="author", lazy="dynamic") threads = db.relationship("Thread", backref="author", lazy="dynamic")
@ -275,9 +277,6 @@ class UserEmailVerification(db.Model):
class NotificationType(enum.Enum): class NotificationType(enum.Enum):
# Any other
OTHER = 0
# Package / release / etc # Package / release / etc
PACKAGE_EDIT = 1 PACKAGE_EDIT = 1
@ -302,6 +301,9 @@ class NotificationType(enum.Enum):
# Editor misc # Editor misc
EDITOR_MISC = 8 EDITOR_MISC = 8
# Any other
OTHER = 0
def getTitle(self): def getTitle(self):
return self.name.replace("_", " ").title() return self.name.replace("_", " ").title()
@ -309,6 +311,29 @@ class NotificationType(enum.Enum):
def toName(self): def toName(self):
return self.name.lower() return self.name.lower()
def get_description(self):
if self == NotificationType.OTHER:
return "Minor notifications not important enough for a dedicated category."
elif self == NotificationType.PACKAGE_EDIT:
return "When another user edits your packages, releases, etc."
elif self == NotificationType.PACKAGE_APPROVAL:
return "Notifications from editors related to the package approval process."
elif self == NotificationType.NEW_THREAD:
return "When a thread is created on your package."
elif self == NotificationType.NEW_REVIEW:
return "When a user posts a review."
elif self == NotificationType.THREAD_REPLY:
return "When someone replies to a thread you're watching."
elif self == NotificationType.MAINTAINER:
return "When your package's maintainers change."
elif self == NotificationType.EDITOR_ALERT:
return "Important editor alerts."
elif self == NotificationType.EDITOR_MISC:
return "Miscellaneous editor alerts."
else:
return ""
def __str__(self): def __str__(self):
return self.name return self.name
@ -353,6 +378,42 @@ class Notification(db.Model):
self.url = url self.url = url
self.package = package self.package = package
def can_send_email(self):
prefs = self.user.notification_preferences
return prefs and getattr(prefs, "pref_" + self.type.toName()) == 2
class UserNotificationPreferences(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship("User", back_populates="notification_preferences")
# 2 = immediate emails
# 1 = daily digest emails
# 0 = no emails
pref_package_edit = db.Column(db.Integer, nullable=False)
pref_package_approval = db.Column(db.Integer, nullable=False)
pref_new_thread = db.Column(db.Integer, nullable=False)
pref_new_review = db.Column(db.Integer, nullable=False)
pref_thread_reply = db.Column(db.Integer, nullable=False)
pref_maintainer = db.Column(db.Integer, nullable=False)
pref_editor_alert = db.Column(db.Integer, nullable=False)
pref_editor_misc = db.Column(db.Integer, nullable=False)
pref_other = db.Column(db.Integer, nullable=False)
def __init__(self, user):
self.user = user
self.pref_package_edit = 1
self.pref_package_approval = 2
self.pref_new_thread = 2
self.pref_new_review = 1
self.pref_thread_reply = 2
self.pref_maintainer = 2
self.pref_editor_alert = 2
self.pref_editor_misc = 0
self.pref_other = 0
class License(db.Model): class License(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)

@ -0,0 +1,32 @@
{% extends "users/settings_base.html" %}
{% block title %}
{{ _("Email and Notifications | %(username)s", username=user.username) }}
{% endblock %}
{% block pane %}
<h2 class="mt-0">{{ _("Email and Notifications") }}</h2>
{% from "macros/forms.html" import render_field, render_submit_field, render_checkbox_field %}
<form action="" method="POST" class="form" role="form">
{{ form.hidden_tag() }}
<table class="table">
<tr>
<th>Event</th>
<th>Description</th>
<td>Emails?</td>
</tr>
{% for type in types %}
<tr>
<td>{{ type.getTitle() }}</td>
<td>{{ type.get_description() }}</td>
<td>{{ render_checkbox_field(form["pref_" + type.toName()]) }}</td>
</tr>
{% endfor %}
</table>
{{ render_submit_field(form.submit, tabindex=280) }}
</form>
{% endblock %}

@ -0,0 +1,42 @@
"""empty message
Revision ID: 5d7233cf8a00
Revises: 81de25b72f66
Create Date: 2020-12-05 03:50:18.843494
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '5d7233cf8a00'
down_revision = '81de25b72f66'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user_notification_preferences',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('pref_other', sa.Integer(), nullable=False),
sa.Column('pref_package_edit', sa.Integer(), nullable=False),
sa.Column('pref_package_approval', sa.Integer(), nullable=False),
sa.Column('pref_new_thread', sa.Integer(), nullable=False),
sa.Column('pref_new_review', sa.Integer(), nullable=False),
sa.Column('pref_thread_reply', sa.Integer(), nullable=False),
sa.Column('pref_maintainer', sa.Integer(), nullable=False),
sa.Column('pref_editor_alert', sa.Integer(), nullable=False),
sa.Column('pref_editor_misc', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('user_notification_preferences')
# ### end Alembic commands ###