diff --git a/calsocial/__init__.py b/calsocial/__init__.py index 6623abd..1f860d3 100644 --- a/calsocial/__init__.py +++ b/calsocial/__init__.py @@ -288,4 +288,16 @@ class CalendarSocialApp(Flask): return redirect(url_for('display_profile', username=username)) + @staticmethod + @route('/notifications') + def notifications(): + from .models import Notification + + if current_user.is_authenticated: + notifs = Notification.query.filter(Notification.profile == current_user.profile) + else: + notifs = [] + + return render_template('notifications.html', notifs=notifs) + app = CalendarSocialApp(__name__) diff --git a/calsocial/models.py b/calsocial/models.py index 3c1a6b1..d8a6708 100644 --- a/calsocial/models.py +++ b/calsocial/models.py @@ -18,6 +18,7 @@ """ from datetime import datetime +from enum import Enum from warnings import warn from flask_babelex import lazy_gettext @@ -41,6 +42,24 @@ def generate_uuid(): return uuid4().hex +def _(translate): # pylint: disable=invalid-name + """Function to mark strings as translatable + + The actual translation will be fetched later in `:meth:AuditLog.get_message`. + """ + + return translate + + +class NotificationAction(Enum): + follow = 1 + + +notification_action_messages = { + NotificationAction.follow: (_('%(actor)s followed you'), _('%(actor)s followed %(item)s')) +} + + class SettingsProxy: """Proxy object to get settings for a user """ @@ -184,6 +203,15 @@ class Profile(db.Model): # pylint: disable=too-few-public-methods #: The display name display_name = db.Column(db.Unicode(length=80), nullable=False) + @property + def fqdn(self): + ret = '' + + if self.user: + return f'@{self.user.username}' + + return f'@{self.username}@{self.domain}' + def __repr__(self): if self.user: username = self.user.username @@ -194,6 +222,16 @@ class Profile(db.Model): # pylint: disable=too-few-public-methods return f'' + def __str__(self): + ret = '' + + if self.display_name: + ret = self.display_name + ' ' + + ret += f'({self.fqdn})' + + return ret + @property def followed_list(self): """List of profiles this profile is following @@ -313,15 +351,6 @@ class UserSetting(db.Model): # pylint: disable=too-few-public-methods return f'' -def _(translate): # pylint: disable=invalid-name - """Function to mark strings as translatable - - The actual translation will be fetched later in `:meth:AuditLog.get_message`. - """ - - return translate - - class AuditLog(db.Model): """Database model for audit log records """ @@ -442,3 +471,62 @@ class UserFollow(db.Model): #: The timestamp when the follow was accepted accepted_at = db.Column(db.DateTime(), nullable=True) + + +class Notification(db.Model): + """Database model for notifications + """ + + __tablename__ = 'notifications' + # pylint: disable=invalid-name + id = db.Column(db.Integer(), primary_key=True) + + #: The recipient of the notification + profile_id = db.Column(db.Integer(), db.ForeignKey('profiles.id'), nullable=False) + + profile = db.relationship('Profile', + backref=db.backref('notifications', lazy='dynamic'), + foreign_keys=[profile_id]) + + #: The profile that generated the notification + actor_id = db.Column(db.Integer(), db.ForeignKey('profiles.id'), nullable=True) + + actor = db.relationship('Profile', foreign_keys=[actor_id]) + + #: The item (e.g. event) that generated the notification + item_id = db.Column(db.Integer(), nullable=True) + + #: The type of the item that generated the notification + item_type = db.Column(db.String(length=40)) + + #: The type of action + action = db.Column(db.Enum(NotificationAction)) + + #: The timestamp when the notification was created + created_at = db.Column(db.DateTime(), default=datetime.utcnow, nullable=False) + + #: The timestamp when the notification was read + read_at = db.Column(db.DateTime(), nullable=True) + + @property + def item(self): + item_class = self._decl_class_registry.get(self.item_type) + + if item_class is None: + warn(f'Unknown item type {self.item_type}') + + return None + + return item_class.query.get(self.item_id) + + @property + def message(self): + """Get the translated message for ``key`` + """ + + from flask_security import current_user + + messages = notification_action_messages.get(self.action) + message = messages[0 if self.item == current_user.profile else 1] + + return lazy_gettext(message, actor=self.actor, item=self.item) diff --git a/calsocial/templates/notifications.html b/calsocial/templates/notifications.html new file mode 100644 index 0000000..3a686b5 --- /dev/null +++ b/calsocial/templates/notifications.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} + +{% block content %} +{% for notif in notifs %} +{{ notif.message }}
+{% endfor %} +{% endblock content %}