From 5a2c5f626e7ed382071ce9538861754278c5c0c2 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Fri, 18 Sep 2020 03:33:12 +0530 Subject: [PATCH] Merge comment notification types --- funnel/models/commentvote.py | 17 +++- funnel/models/notification_types.py | 33 ++------ funnel/views/commentvote.py | 19 ++--- .../notifications/commentvote_notification.py | 32 +++---- ...02ced3_merge_comment_notification_types.py | 84 +++++++++++++++++++ ...update_comment_notification_preferences.py | 43 ++++++++++ 6 files changed, 173 insertions(+), 55 deletions(-) create mode 100644 migrations/versions/1bd91b02ced3_merge_comment_notification_types.py create mode 100644 migrations/versions/3847982f1472_update_comment_notification_preferences.py diff --git a/funnel/models/commentvote.py b/funnel/models/commentvote.py index 44b0e60ab..ad88477c3 100644 --- a/funnel/models/commentvote.py +++ b/funnel/models/commentvote.py @@ -29,6 +29,7 @@ class COMMENT_STATE(LabeledEnum): # NOQA: N801 # What is this Voteset or Commentset attached to? +# TODO: Deprecated, doesn't help as much as we thought it would class SET_TYPE: # NOQA: N801 PROJECT = 0 PROPOSAL = 2 @@ -112,7 +113,12 @@ class Commentset(UuidMixin, BaseMixin, db.Model): settype = db.Column('type', db.Integer, nullable=True) count = db.Column(db.Integer, default=0, nullable=False) - __roles__ = {'all': {'read': {'settype', 'count'}}} + __roles__ = { + 'all': { + 'read': {'settype', 'count', 'project', 'proposal'}, + 'call': {'url_for'}, + } + } __datasets__ = { 'primary': {'settype', 'count'}, @@ -123,6 +129,7 @@ def __init__(self, **kwargs): super(Commentset, self).__init__(**kwargs) self.count = 0 + @with_roles(read={'all'}) @property def parent(self): # FIXME: Move this to a CommentMixin that uses a registry, like EmailAddress @@ -133,6 +140,14 @@ def parent(self): parent = self.proposal return parent + @with_roles(read={'all'}) + @property + def parent_type(self): + parent = self.parent + if parent: + return parent.__tablename__ + return None + def permissions(self, user, inherited=None): perms = super().permissions(user, inherited) if user is not None: diff --git a/funnel/models/notification_types.py b/funnel/models/notification_types.py index 7d8a71038..dcc34a23d 100644 --- a/funnel/models/notification_types.py +++ b/funnel/models/notification_types.py @@ -1,7 +1,7 @@ from baseframe import __ from funnel.models.moderation import CommentModeratorReport -from .commentvote import Comment +from .commentvote import Comment, Commentset from .notification import Notification, notification_categories from .organization_membership import OrganizationMembership from .project import Project @@ -17,8 +17,7 @@ 'NewUpdateNotification', 'CommentReportReceivedNotification', 'CommentReplyNotification', - 'ProjectCommentNotification', - 'ProposalCommentNotification', + 'NewCommentNotification', 'ProposalReceivedNotification', 'ProposalSubmittedNotification', 'RegistrationCancellationNotification', @@ -161,35 +160,17 @@ class CommentReplyNotification(Notification): roles = ['replied_to_commenter'] -class ProjectCommentNotification(DocumentHasProfile, Notification): - """Notification of comments on a project.""" +class NewCommentNotification(Notification): + """Notification of new comment.""" - __mapper_args__ = {'polymorphic_identity': 'comment_project'} + __mapper_args__ = {'polymorphic_identity': 'comment_new'} category = notification_categories.project_crew - title = __("When my project receives a comment") + title = __("When my project or proposal receives a comment") exclude_actor = True - document_model = Project - fragment_model = Comment - # Note: These roles must be available on Comment, not Proposal. Roles come from - # fragment if present, document if not. - roles = ['replied_to_commenter', 'document_subscriber'] - - -class ProposalCommentNotification(DocumentHasProject, Notification): - """Notification of comments on a proposal.""" - - __mapper_args__ = {'polymorphic_identity': 'comment_proposal'} - - category = notification_categories.participant - title = __("When my proposal receives a comment") - exclude_actor = True - - document_model = Proposal + document_model = Commentset fragment_model = Comment - # Note: These roles must be available on Comment, not Proposal. Roles come from - # fragment if present, document if not. roles = ['replied_to_commenter', 'document_subscriber'] diff --git a/funnel/views/commentvote.py b/funnel/views/commentvote.py index 712c39c5f..8b88dabe1 100644 --- a/funnel/views/commentvote.py +++ b/funnel/views/commentvote.py @@ -22,10 +22,8 @@ CommentReplyNotification, CommentReportReceivedNotification, Commentset, - Project, - ProjectCommentNotification, + NewCommentNotification, Proposal, - ProposalCommentNotification, Voteset, db, ) @@ -37,15 +35,6 @@ ProposalComment = namedtuple('ProposalComment', ['proposal', 'comment']) -def comment_notification_type(comment): - # FIXME: Move this into a CommentMixin model - parent = comment.commentset.parent - if isinstance(parent, Project): - return ProjectCommentNotification(document=parent, fragment=comment) - if isinstance(parent, Proposal): - return ProposalCommentNotification(document=parent, fragment=comment) - - @Comment.views('url') def comment_url(obj): url = None @@ -166,7 +155,9 @@ def new_comment(self): comment.voteset.vote(current_auth.user) # Vote for your own comment db.session.add(comment) db.session.commit() - dispatch_notification(comment_notification_type(comment)) + dispatch_notification( + NewCommentNotification(document=comment.commentset, fragment=comment) + ) return { 'status': 'ok', 'message': _("Your comment has been posted"), @@ -235,7 +226,7 @@ def reply(self): CommentReplyNotification( document=comment.in_reply_to, fragment=comment ), - comment_notification_type(comment), + NewCommentNotification(document=comment.commentset, fragment=comment), ) return { 'status': 'ok', diff --git a/funnel/views/notifications/commentvote_notification.py b/funnel/views/notifications/commentvote_notification.py index 5c7fde201..66cf504d6 100644 --- a/funnel/views/notifications/commentvote_notification.py +++ b/funnel/views/notifications/commentvote_notification.py @@ -6,8 +6,7 @@ from ...models import ( CommentReplyNotification, CommentReportReceivedNotification, - ProjectCommentNotification, - ProposalCommentNotification, + NewCommentNotification, ) from ..helpers import shortlink from ..notification import RenderNotification @@ -48,8 +47,7 @@ def sms(self): @CommentReplyNotification.renderer -@ProjectCommentNotification.renderer -@ProposalCommentNotification.renderer +@NewCommentNotification.renderer class CommentNotification(RenderNotification): """Render comment notifications for various document types.""" @@ -74,34 +72,40 @@ def commenters(self): user_ids.add(comment.user.uuid) return users + @property + def document_type(self): + if self.notification.document_type == 'comment': + return 'comment' + return self.document.parent_type + def document_comments_url(self, **kwargs): """URL to comments view on the document.""" - if self.notification.document_type == 'project': - return self.document.url_for('comments', **kwargs) - if self.notification.document_type == 'proposal': - return self.document.url_for('view', **kwargs) + '#comments' + if self.document_type == 'project': + return self.document.parent.url_for('comments', **kwargs) + if self.document_type == 'proposal': + return self.document.parent.url_for('view', **kwargs) + '#comments' return self.document.url_for('view', **kwargs) def activity_template_standalone(self, comment=None): """Activity template for standalone use, such as email subject.""" if comment is None: comment = self.comment - if self.notification.document_type == 'comment': + if self.document_type == 'comment': return _("{actor} replied to your comment") - if self.notification.document_type == 'project': + if self.document_type == 'project': return _("{actor} commented on your project") - if self.notification.document_type == 'proposal': + if self.document_type == 'proposal': return _("{actor} commented on your proposal") def activity_template_inline(self, comment=None): """Activity template for inline use with other content, like SMS with URL.""" if comment is None: comment = self.comment - if self.notification.document_type == 'comment': + if self.document_type == 'comment': return _("{actor} replied to your comment:") - if self.notification.document_type == 'project': + if self.document_type == 'project': return _("{actor} commented on your project:") - if self.notification.document_type == 'proposal': + if self.document_type == 'proposal': return _("{actor} commented on your proposal:") def activity_html(self, comment=None): diff --git a/migrations/versions/1bd91b02ced3_merge_comment_notification_types.py b/migrations/versions/1bd91b02ced3_merge_comment_notification_types.py new file mode 100644 index 000000000..7c2ac1c36 --- /dev/null +++ b/migrations/versions/1bd91b02ced3_merge_comment_notification_types.py @@ -0,0 +1,84 @@ +"""Merge comment notification types + +Revision ID: 1bd91b02ced3 +Revises: 4845fd12dbfd +Create Date: 2020-09-18 02:44:20.827703 + +""" + +from alembic import op +from sqlalchemy.dialects import postgresql +from sqlalchemy.sql import column, table +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '1bd91b02ced3' +down_revision = '4845fd12dbfd' +branch_labels = None +depends_on = None + + +notification = table( + 'notification', + column('eventid', postgresql.UUID()), + column('id', postgresql.UUID()), + column('type', sa.Unicode()), + column('document_uuid', postgresql.UUID()), + column('fragment_uuid', postgresql.UUID()), +) + +project = table( + 'project', + column('id', sa.Integer()), + column('uuid', postgresql.UUID()), + column('commentset_id', sa.Integer()), +) + +proposal = table( + 'proposal', + column('id', sa.Integer()), + column('uuid', postgresql.UUID()), + column('commentset_id', sa.Integer()), +) + +commentset = table( + 'commentset', + column('id', sa.Integer()), + column('uuid', postgresql.UUID()), +) + + +def upgrade(): + op.execute( + notification.update() + .values(type='comment_new', document_uuid=commentset.c.uuid) + .where(notification.c.type == 'comment_project') + .where(notification.c.document_uuid == project.c.uuid) + .where(project.c.commentset_id == commentset.c.id) + ) + + op.execute( + notification.update() + .values(type='comment_new', document_uuid=commentset.c.uuid) + .where(notification.c.type == 'comment_proposal') + .where(notification.c.document_uuid == proposal.c.uuid) + .where(proposal.c.commentset_id == commentset.c.id) + ) + + +def downgrade(): + op.execute( + notification.update() + .values(type='comment_proposal', document_uuid=proposal.c.uuid) + .where(notification.c.type == 'comment_new') + .where(notification.c.document_uuid == commentset.c.uuid) + .where(proposal.c.commentset_id == commentset.c.id) + ) + + op.execute( + notification.update() + .values(type='comment_project', document_uuid=project.c.uuid) + .where(notification.c.type == 'comment_new') + .where(notification.c.document_uuid == commentset.c.uuid) + .where(project.c.commentset_id == commentset.c.id) + ) diff --git a/migrations/versions/3847982f1472_update_comment_notification_preferences.py b/migrations/versions/3847982f1472_update_comment_notification_preferences.py new file mode 100644 index 000000000..399254b41 --- /dev/null +++ b/migrations/versions/3847982f1472_update_comment_notification_preferences.py @@ -0,0 +1,43 @@ +"""Update comment notification preferences + +Revision ID: 3847982f1472 +Revises: 1bd91b02ced3 +Create Date: 2020-09-18 03:27:44.871342 + +""" + +from alembic import op +from sqlalchemy.sql import column, table + +# revision identifiers, used by Alembic. +revision = '3847982f1472' +down_revision = '1bd91b02ced3' +branch_labels = None +depends_on = None + +notification_preferences = table( + 'notification_preferences', + column('notification_type'), +) + + +def upgrade(): + op.execute( + notification_preferences.update() + .values(notification_type='comment_new') + .where(notification_preferences.c.notification_type == 'comment_project') + ) + + op.execute( + notification_preferences.delete().where( + notification_preferences.c.notification_type == 'comment_proposal' + ) + ) + + +def downgrade(): + op.execute( + notification_preferences.update() + .values(notification_type='comment_project') + .where(notification_preferences.c.notification_type == 'comment_new') + )