From b823fa5c5d38a4b583a34f528b104792b6c93fd7 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 25 Feb 2020 18:09:10 +0100 Subject: [PATCH 01/40] :sparkles: add subscribe to posts routes n methods --- openbook/urls.py | 4 ++- openbook_auth/checkers.py | 30 ++++++++++++++++- openbook_auth/models.py | 39 ++++++++++++++++++++- openbook_posts/views/post/serializers.py | 20 +++++++++++ openbook_posts/views/post/views.py | 43 +++++++++++++++++++++++- 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/openbook/urls.py b/openbook/urls.py index 59cf73eb..c943cfca 100644 --- a/openbook/urls.py +++ b/openbook/urls.py @@ -72,7 +72,8 @@ from openbook_notifications.views import Notifications, NotificationItem, ReadNotifications, ReadNotification, \ UnreadNotificationsCount from openbook_posts.views.post.views import PostItem, PostOpen, PostClose, MutePost, UnmutePost, TranslatePost, \ - PostPreviewLinkData, SearchPostParticipants, GetPostParticipants, PublishPost, PostStatus + PostPreviewLinkData, SearchPostParticipants, GetPostParticipants, PublishPost, PostStatus, \ + SubscribeToPostNotifications from openbook_posts.views.post_comment.post_comment_reaction.views import PostCommentReactionItem from openbook_posts.views.post_comment.post_comment_reactions.views import PostCommentReactions, \ PostCommentReactionsEmojiCount @@ -159,6 +160,7 @@ post_notifications_patterns = [ path('mute/', MutePost.as_view(), name='mute-post'), path('unmute/', UnmutePost.as_view(), name='unmute-post'), + path('subscribe/', SubscribeToPostNotifications.as_view(), name='subscribe-post'), ] post_comment_reactions_patterns = [ diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index bbc04306..0024538d 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -5,7 +5,8 @@ from openbook_common.utils.model_loaders import get_post_model, get_community_model, get_post_comment_model, \ get_language_model, get_user_model, get_emoji_group_model, get_post_reaction_model, get_user_invite_model, \ - get_community_notifications_subscription_model, get_user_notifications_subscription_model + get_community_notifications_subscription_model, get_user_notifications_subscription_model, \ + get_post_notifications_subscription_model from openbook_common import checkers as common_checkers @@ -342,6 +343,33 @@ def check_can_disable_new_post_notifications_for_community(user, community): ) +def check_can_disable_post_notifications_for_post(user, post): + PostNotificationsSubscription = get_post_notifications_subscription_model() + post_notifications_enabled = \ + PostNotificationsSubscription.is_user_with_username_subscribed_to_notifications_for_post_with_id( + username=user.username, post_id=post.pk) + + if not post_notifications_enabled: + raise ValidationError( + _('Post notifications are not enabled'), + ) + + +def check_can_enable_post_notifications_for_post(user, post): + if not user.can_see_post(post=post): + raise ValidationError( + _('You do not have permissions to view this post.'), + ) + + PostNotificationsSubscription = get_post_notifications_subscription_model() + post_notifications_enabled = \ + PostNotificationsSubscription.is_user_with_username_subscribed_to_notifications_for_post_with_id( + username=user.username, post_id=post.pk) + + if post_notifications_enabled: + raise ValidationError('Post notifications are already enabled.') + + def check_can_get_community_with_name_members(user, community_name): check_is_not_banned_from_community_with_name(user=user, community_name=community_name) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 0941cbf6..3e7290b9 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -36,7 +36,8 @@ get_post_comment_reply_notification_model, get_moderated_object_model, get_moderation_report_model, \ get_moderation_penalty_model, get_post_comment_mute_model, get_post_comment_reaction_model, \ get_post_comment_reaction_notification_model, get_top_post_model, get_top_post_community_exclusion_model, \ - get_hashtag_model, get_profile_posts_community_exclusion_model, get_user_new_post_notification_model + get_hashtag_model, get_profile_posts_community_exclusion_model, get_user_new_post_notification_model, \ + get_post_notifications_subscription_model from openbook_common.validators import name_characters_validator from openbook_notifications import helpers from openbook_auth.checkers import * @@ -2187,6 +2188,42 @@ def close_post(self, post): return post + def enable_post_notifications_for_post_with_id(self, post_id): + Post = get_post_model() + post = Post.objects.get(id=post_id) + PostNotificationsSubscription = get_post_notifications_subscription_model() + + check_can_enable_post_notifications_for_post(user=self, post=post) + + post_notifications_subscription = PostNotificationsSubscription. \ + get_or_create_post_notifications_subscription(post=post, subscriber=self) + + post_notifications_subscription.post_notifications = True + post_notifications_subscription.save() + + return post + + def disable_post_notifications_for_post_with_id(self, post_id): + Post = get_post_model() + post = Post.objects.get(id=post_id) + PostNotificationsSubscription = get_post_notifications_subscription_model() + + check_can_disable_post_notifications_for_post(user=self, post=post) + + post_notifications_subscription = PostNotificationsSubscription.\ + get_or_create_post_notifications_subscription(post=post, subscriber=self) + + post_notifications_subscription.post_notifications = False + post_notifications_subscription.save() + + return post + + def are_post_notifications_enabled_for_post(self, post): + PostNotificationsSubscription = get_post_notifications_subscription_model() + return PostNotificationsSubscription.are_post_notifications_enabled_for_user_with_username_and_post_with_id( + username=self.username, post_id=post.pk + ) + def get_hashtag_with_name(self, hashtag_name): Hashtag = get_hashtag_model() hashtag = Hashtag.objects.get(name=hashtag_name) diff --git a/openbook_posts/views/post/serializers.py b/openbook_posts/views/post/serializers.py index 29e1fa8b..53c2d7a0 100644 --- a/openbook_posts/views/post/serializers.py +++ b/openbook_posts/views/post/serializers.py @@ -10,6 +10,7 @@ from openbook_communities.models import CommunityMembership, Community from openbook_communities.serializers_fields import CommunityMembershipsField from openbook_posts.models import PostImage, Post +from openbook_posts.serializer_fields import PostNotificationsEnabledField from openbook_posts.validators import post_uuid_exists, post_text_validators from openbook_posts.views.post_reaction.serializers import PostReactionSerializer @@ -213,6 +214,13 @@ class ClosePostSerializer(serializers.Serializer): ) +class SubscribeToPostNotificationsSerializer(serializers.Serializer): + post_uuid = serializers.UUIDField( + validators=[post_uuid_exists], + required=True, + ) + + class OpenPostSerializer(serializers.Serializer): post_uuid = serializers.UUIDField( validators=[post_uuid_exists], @@ -290,6 +298,18 @@ class Meta: ) +class SubscribeToPostNotificationsPostSerializer(serializers.ModelSerializer): + post_notifications_enabled = PostNotificationsEnabledField() + + class Meta: + model = Post + fields = ( + 'id', + 'uuid', + 'post_notifications_enabled', + ) + + class PublishPostSerializer(serializers.Serializer): post_uuid = serializers.UUIDField( validators=[post_uuid_exists], diff --git a/openbook_posts/views/post/views.py b/openbook_posts/views/post/views.py index a01850d5..899ea9e9 100644 --- a/openbook_posts/views/post/views.py +++ b/openbook_posts/views/post/views.py @@ -14,7 +14,7 @@ OpenClosePostSerializer, \ OpenPostSerializer, ClosePostSerializer, TranslatePostSerializer, \ SearchPostParticipantsSerializer, PostParticipantSerializer, GetPostParticipantsSerializer, PublishPostSerializer, \ - GetPostStatusSerializer + GetPostStatusSerializer, SubscribeToPostNotificationsSerializer, SubscribeToPostNotificationsPostSerializer from openbook_translation.strategies.base import TranslationClientError, UnsupportedLanguagePairException, \ MaxTextLengthExceededError @@ -321,3 +321,44 @@ def get(self, request, post_uuid): return Response(preview_link_data, status=status.HTTP_200_OK) + +class SubscribeToPostNotifications(APIView): + permission_classes = (IsAuthenticated,) + + def put(self, request, post_uuid): + request_data = request.data.copy() + request_data['post_uuid'] = post_uuid + + serializer = SubscribeToPostNotificationsSerializer(data=request_data) + serializer.is_valid(raise_exception=True) + + user = request.user + data = serializer.validated_data + post_uuid = data.get('post_uuid') + post_id = get_post_id_for_post_uuid(post_uuid) + + with transaction.atomic(): + post = user.enable_post_notifications_for_post_with_id(post_id=post_id) + + response_serializer = SubscribeToPostNotificationsPostSerializer(post, context={"request": request}) + + return Response(response_serializer.data, status=status.HTTP_201_CREATED) + + def delete(self, request, post_uuid): + request_data = request.data.copy() + request_data['post_uuid'] = post_uuid + + serializer = SubscribeToPostNotificationsSerializer(data=request_data) + serializer.is_valid(raise_exception=True) + data = serializer.validated_data + + user = request.user + post_uuid = data.get('post_uuid') + post_id = get_post_id_for_post_uuid(post_uuid) + + with transaction.atomic(): + post = user.disable_post_notifications_for_post_with_id(post_id=post_id) + + response_serializer = SubscribeToPostNotificationsPostSerializer(post, context={"request": request}) + + return Response(response_serializer.data, status=status.HTTP_200_OK) From e9060a2d551835d49f75f7d1f9bdf8046487273d Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 25 Feb 2020 18:09:49 +0100 Subject: [PATCH 02/40] :sparkles: add PostNotificationsSubscription model --- openbook_common/utils/model_loaders.py | 4 ++ .../0069_postnotificationssubscription.py | 28 ++++++++++ openbook_posts/models.py | 55 +++++++++++++++++++ openbook_posts/serializer_fields.py | 17 ++++++ 4 files changed, 104 insertions(+) create mode 100644 openbook_posts/migrations/0069_postnotificationssubscription.py create mode 100644 openbook_posts/serializer_fields.py diff --git a/openbook_common/utils/model_loaders.py b/openbook_common/utils/model_loaders.py index 623a6ac6..70d7c070 100644 --- a/openbook_common/utils/model_loaders.py +++ b/openbook_common/utils/model_loaders.py @@ -37,6 +37,10 @@ def get_post_media_model(): return apps.get_model('openbook_posts.PostMedia') +def get_post_notifications_subscription_model(): + return apps.get_model('openbook_posts.PostNotificationsSubscription') + + def get_proxy_blacklist_domain_model(): return apps.get_model('openbook_common.ProxyBlacklistedDomain') diff --git a/openbook_posts/migrations/0069_postnotificationssubscription.py b/openbook_posts/migrations/0069_postnotificationssubscription.py new file mode 100644 index 00000000..1643f583 --- /dev/null +++ b/openbook_posts/migrations/0069_postnotificationssubscription.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.5 on 2020-02-25 16:53 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('openbook_posts', '0068_profilepostscommunityexclusion'), + ] + + operations = [ + migrations.CreateModel( + name='PostNotificationsSubscription', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('post_notifications', models.BooleanField(default=False)), + ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications_subscriptions', to='openbook_posts.Post')), + ('subscriber', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='post_notifications_subscriptions', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('post', 'subscriber')}, + }, + ), + ] diff --git a/openbook_posts/models.py b/openbook_posts/models.py index 7918e090..cc4d8d81 100644 --- a/openbook_posts/models.py +++ b/openbook_posts/models.py @@ -1300,3 +1300,58 @@ def create_post_comment_user_mention(cls, user, post_comment): owner_id=user.pk) send_post_comment_user_mention_push_notification(post_comment_user_mention=post_comment_user_mention) return post_comment_user_mention + + +class PostNotificationsSubscription(models.Model): + subscriber = models.ForeignKey(User, on_delete=models.CASCADE, related_name='post_notifications_subscriptions', + null=False, + blank=False) + post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='notifications_subscriptions', null=False, + blank=False) + post_notifications = models.BooleanField(default=False, blank=False) + + class Meta: + unique_together = ('post', 'subscriber',) + + @classmethod + def create_post_notifications_subscription(cls, subscriber, post): + if not cls.objects.filter(subscriber=subscriber, post=post).exists(): + return cls.objects.create(subscriber=subscriber, post=post) + + post_notifications_subscription = cls.objects.get(subscriber=subscriber, post=post) + post_notifications_subscription.save() + + return post_notifications_subscription + + @classmethod + def get_or_create_post_notifications_subscription(cls, subscriber, post): + try: + post_notifications_subscription = cls.objects.get(subscriber_id=subscriber.pk, + post_id=post.pk) + except cls.DoesNotExist: + post_notifications_subscription = cls.create_post_notifications_subscription( + subscriber=subscriber, + post=post + ) + + return post_notifications_subscription + + @classmethod + def delete_post_notifications_subscription(cls, subscriber, post): + return cls.objects.filter(subscriber=subscriber, post=post).delete() + + @classmethod + def post_notifications_subscription_exists(cls, subscriber, post): + return cls.objects.filter(subscriber=subscriber, post=post).exists() + + @classmethod + def is_user_with_username_subscribed_to_notifications_for_post_with_id(cls, username, post_id): + return cls.objects.filter(post_id=post_id, + subscriber__username=username, + post_notifications=True).exists() + + @classmethod + def are_post_notifications_enabled_for_user_with_username_and_post_with_id(cls, username, post_id): + return cls.objects.filter(post_id=post_id, + subscriber__username=username, + post_notifications=True).exists() diff --git a/openbook_posts/serializer_fields.py b/openbook_posts/serializer_fields.py new file mode 100644 index 00000000..bd5386ad --- /dev/null +++ b/openbook_posts/serializer_fields.py @@ -0,0 +1,17 @@ +from rest_framework.fields import Field + + +class PostNotificationsEnabledField(Field): + def __init__(self, **kwargs): + kwargs['source'] = '*' + kwargs['read_only'] = True + super(PostNotificationsEnabledField, self).__init__(**kwargs) + + def to_representation(self, post): + request = self.context.get('request') + request_user = request.user + + if request_user.is_anonymous: + return False + + return request_user.are_post_notifications_enabled_for_post(post=post) From fecc1d60d76f9182f9e7033c350d85fe40db7188 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:24:51 +0100 Subject: [PATCH 03/40] :sparkles: add post subscribers to comment n reply notification target users --- openbook_posts/models.py | 54 +++++++++++++++++++---------- openbook_posts/serializer_fields.py | 17 --------- 2 files changed, 35 insertions(+), 36 deletions(-) delete mode 100644 openbook_posts/serializer_fields.py diff --git a/openbook_posts/models.py b/openbook_posts/models.py index cc4d8d81..88d0d0d6 100644 --- a/openbook_posts/models.py +++ b/openbook_posts/models.py @@ -246,18 +246,27 @@ def _get_trending_posts_old_query(cls): def get_post_comment_notification_target_users(cls, post, post_commenter): """ Returns the users that should be notified of a post comment. - This includes the post creator and other post commenters + This includes the post creator and other post commenters and post subscribers :return: """ - # Add other post commenters, exclude replies to comments, the post commenter - other_commenters = User.objects.filter( - Q(posts_comments__post_id=post.pk, posts_comments__parent_comment_id=None, ) & ~Q( - id=post_commenter.pk)) + # Add other post commenters and subscribers, exclude replies to comments, the post commenter - post_creator = User.objects.filter(pk=post.creator_id) + # post subscribers + post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk, + post_notifications_subscriptions__comment_notifications=True) - return other_commenters.union(post_creator) + other_commenters_query = Q(posts_comments__post_id=post.pk, posts_comments__parent_comment_id=None,) + + other_target_users = User.objects.\ + only('id', 'username', 'notifications_settings__post_comment_notifications').\ + filter((post_subscribers_query | other_commenters_query) & ~Q(id=post_commenter.pk)) + + post_creator = User.objects. \ + only('id', 'username', 'notifications_settings__post_comment_notifications'). \ + filter(pk=post.creator_id) + + return other_target_users.union(post_creator) @classmethod def get_post_comment_reply_notification_target_users(cls, post_commenter, parent_post_comment): @@ -265,18 +274,25 @@ def get_post_comment_reply_notification_target_users(cls, post_commenter, parent Returns the users that should be notified of a post comment reply. :return: """ + post = parent_post_comment.post + # post subscribers + post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk, + post_notifications_subscriptions__comment_notifications=True) + other_repliers_query = Q(posts_comments__parent_comment_id=parent_post_comment.pk, ) # Add other post commenters, exclude non replies, the post commenter - other_repliers = User.objects.filter( - Q(posts_comments__parent_comment_id=parent_post_comment.pk, ) & ~Q( - id=post_commenter.pk)) + other_repliers = User.objects.\ + only('id', 'username', 'notifications_settings__post_comment_reply_notifications').\ + filter((post_subscribers_query | other_repliers_query) & ~Q(id=post_commenter.pk)) # Add post comment creator - post_comment_creator = User.objects.filter(pk=parent_post_comment.commenter_id) - + post_comment_creator = User.objects. \ + only('id', 'username', 'notifications_settings__post_comment_reply_notifications'). \ + filter(pk=parent_post_comment.commenter_id) # Add post creator - post = parent_post_comment.post - post_creator = User.objects.filter(pk=post.creator.id) + post_creator = User.objects. \ + only('id', 'username', 'notifications_settings__post_comment_reply_notifications'). \ + filter(pk=post.creator.id) return other_repliers.union(post_comment_creator, post_creator) @classmethod @@ -1308,7 +1324,7 @@ class PostNotificationsSubscription(models.Model): blank=False) post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='notifications_subscriptions', null=False, blank=False) - post_notifications = models.BooleanField(default=False, blank=False) + comment_notifications = models.BooleanField(default=False, blank=False) class Meta: unique_together = ('post', 'subscriber',) @@ -1345,13 +1361,13 @@ def post_notifications_subscription_exists(cls, subscriber, post): return cls.objects.filter(subscriber=subscriber, post=post).exists() @classmethod - def is_user_with_username_subscribed_to_notifications_for_post_with_id(cls, username, post_id): + def is_user_with_username_subscribed_to_comment_notifications_for_post_with_id(cls, username, post_id): return cls.objects.filter(post_id=post_id, subscriber__username=username, - post_notifications=True).exists() + comment_notifications=True).exists() @classmethod - def are_post_notifications_enabled_for_user_with_username_and_post_with_id(cls, username, post_id): + def are_comment_notifications_enabled_for_user_with_username_and_post_with_id(cls, username, post_id): return cls.objects.filter(post_id=post_id, subscriber__username=username, - post_notifications=True).exists() + comment_notifications=True).exists() diff --git a/openbook_posts/serializer_fields.py b/openbook_posts/serializer_fields.py deleted file mode 100644 index bd5386ad..00000000 --- a/openbook_posts/serializer_fields.py +++ /dev/null @@ -1,17 +0,0 @@ -from rest_framework.fields import Field - - -class PostNotificationsEnabledField(Field): - def __init__(self, **kwargs): - kwargs['source'] = '*' - kwargs['read_only'] = True - super(PostNotificationsEnabledField, self).__init__(**kwargs) - - def to_representation(self, post): - request = self.context.get('request') - request_user = request.user - - if request_user.is_anonymous: - return False - - return request_user.are_post_notifications_enabled_for_post(post=post) From 820eab6ef9373e8c70b3b8b08867773770835166 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:26:06 +0100 Subject: [PATCH 04/40] :recycle: rename checker methods --- openbook_auth/checkers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index 0024538d..adbb36a2 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -346,7 +346,7 @@ def check_can_disable_new_post_notifications_for_community(user, community): def check_can_disable_post_notifications_for_post(user, post): PostNotificationsSubscription = get_post_notifications_subscription_model() post_notifications_enabled = \ - PostNotificationsSubscription.is_user_with_username_subscribed_to_notifications_for_post_with_id( + PostNotificationsSubscription.is_user_with_username_subscribed_to_comment_notifications_for_post_with_id( username=user.username, post_id=post.pk) if not post_notifications_enabled: @@ -363,7 +363,7 @@ def check_can_enable_post_notifications_for_post(user, post): PostNotificationsSubscription = get_post_notifications_subscription_model() post_notifications_enabled = \ - PostNotificationsSubscription.is_user_with_username_subscribed_to_notifications_for_post_with_id( + PostNotificationsSubscription.is_user_with_username_subscribed_to_comment_notifications_for_post_with_id( username=user.username, post_id=post.pk) if post_notifications_enabled: From 3e88108264ebb14309309fd8eb0f23a12c638548 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:27:24 +0100 Subject: [PATCH 05/40] :sparkles: comment n reply post should include subscribers, move notification_messages to helpers --- openbook_auth/models.py | 156 ++++++++++++++---------------- openbook_notifications/helpers.py | 149 +++++++++++++++++++++++++--- 2 files changed, 209 insertions(+), 96 deletions(-) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 3e7290b9..b19b8d58 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -21,12 +21,14 @@ from openbook_auth.helpers import upload_to_user_cover_directory, upload_to_user_avatar_directory from openbook_hashtags.queries import make_search_hashtag_query_for_user_with_id, \ make_get_hashtag_with_name_for_user_with_id_query -from openbook_notifications.helpers import get_notification_language_code_for_target_user +from openbook_notifications.helpers import get_notification_language_code_for_target_user, \ + send_post_comment_push_notification, send_post_notifications_subscription_comment_push_notification, \ + send_post_comment_reply_push_notification from openbook_posts.queries import make_get_hashtag_posts_for_user_with_id_query from openbook_posts.query_collections import get_posts_for_user_collection from openbook_translation import translation_strategy from openbook_common.helpers import get_supported_translation_language -from openbook_common.models import Badge, Language +from openbook_common.models import Badge from openbook_common.utils.helpers import delete_file_field from openbook_common.utils.model_loaders import get_connection_model, get_circle_model, get_follow_model, \ get_list_model, get_community_invite_model, \ @@ -37,7 +39,7 @@ get_moderation_penalty_model, get_post_comment_mute_model, get_post_comment_reaction_model, \ get_post_comment_reaction_notification_model, get_top_post_model, get_top_post_community_exclusion_model, \ get_hashtag_model, get_profile_posts_community_exclusion_model, get_user_new_post_notification_model, \ - get_post_notifications_subscription_model + get_post_subscription_comment_notification_model from openbook_common.validators import name_characters_validator from openbook_notifications import helpers from openbook_auth.checkers import * @@ -534,6 +536,7 @@ def update_notifications_settings(self, post_comment_notifications=None, post_re post_comment_reply_notifications=None, post_comment_user_mention_notifications=None, post_user_mention_notifications=None, + post_subscription_comment_notifications=None, ): notifications_settings = self.notifications_settings @@ -551,6 +554,7 @@ def update_notifications_settings(self, post_comment_notifications=None, post_re post_comment_reply_notifications=post_comment_reply_notifications, post_comment_user_mention_notifications=post_comment_user_mention_notifications, post_user_mention_notifications=post_user_mention_notifications, + post_subscription_comment_notifications=post_subscription_comment_notifications, ) return notifications_settings @@ -801,6 +805,10 @@ def has_comment_notifications_enabled_for_post_with_id(self, post_id): return self.notifications_settings.post_comment_notifications and not self.has_muted_post_with_id( post_id=post_id) + def has_post_subscription_comment_notifications_enabled_for_post_with_id(self, post_id): + return self.notifications_settings.post_subscription_comment_notifications and not \ + self.has_muted_post_with_id(post_id=post_id) + def has_reply_notifications_enabled_for_post_comment(self, post_comment): return self.notifications_settings.post_comment_reply_notifications and not self.has_muted_post_with_id( post_id=post_comment.post_id) and not self.has_muted_post_comment_with_id( @@ -1183,42 +1191,39 @@ def comment_post(self, post, text): # Language should also be prefetched here, for some reason it doesnt work.... post_notification_target_users = Post.get_post_comment_notification_target_users(post=post, - post_commenter=post_commenter).only( - 'id', 'username', 'notifications_settings__post_comment_notifications') + post_commenter=post_commenter) + PostCommentNotification = get_post_comment_notification_model() + PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() for post_notification_target_user in post_notification_target_users: + if post_notification_target_user.pk == post_commenter.pk or \ not post_notification_target_user.can_see_post_comment(post_comment=post_comment): continue post_notification_target_user_is_post_creator = post_notification_target_user.id == post_creator.id - post_notification_target_has_comment_notifications_enabled = post_notification_target_user.has_comment_notifications_enabled_for_post_with_id( - post_id=post_comment.post_id) - - if post_notification_target_has_comment_notifications_enabled: - target_user_language_code = get_notification_language_code_for_target_user( - post_notification_target_user) - with translation.override(target_user_language_code): - if post_notification_target_user_is_post_creator: - notification_message = { - "en": _('%(post_commenter_name)s · %(post_commenter_username)s commented on your post.') % { - 'post_commenter_username': post_commenter.username, - 'post_commenter_name': post_commenter.profile.name, - }} - else: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you also commented on.') % { - 'post_commenter_username': post_commenter.username, - 'post_commenter_name': post_commenter.profile.name, - }} + post_notification_target_user_is_post_subscriber = \ + post_notification_target_user.are_post_subscription_comment_notifications_enabled_for_post(post=post) + + if post_notification_target_user_is_post_creator: + # send post notification + send_post_comment_push_notification(post_comment=post_comment, target_user=post_notification_target_user) + PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, + owner_id=post_notification_target_user.id) + + elif post_notification_target_user_is_post_subscriber: + # send the post subscriber notification + send_post_notifications_subscription_comment_push_notification( + post_comment=post_comment, + target_user=post_notification_target_user) + PostSubscriptionCommentNotification.create_post_subscription_comment_notification( + post_comment_id=post_comment.pk, owner_id=post_notification_target_user.id) - self._send_post_comment_push_notification(post_comment=post_comment, - notification_message=notification_message, - notification_target_user=post_notification_target_user) - - PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, - owner_id=post_notification_target_user.id) + else: + # regular commenter + send_post_comment_push_notification(post_comment=post_comment, target_user=post_notification_target_user) + PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, + owner_id=post_notification_target_user.id) return post_comment @@ -1235,15 +1240,14 @@ def reply_to_comment_for_post(self, post_comment, post, text): comment_creator = post_comment.commenter.id post_creator = post.creator replier = self - post = post_comment.post Post = get_post_model() + PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() # Language should also be prefetched here, for some reason it doesnt work.... post_notification_target_users = Post.get_post_comment_reply_notification_target_users( post_commenter=self, - parent_post_comment=post_comment).only( - 'id', 'username', 'notifications_settings__post_comment_reply_notifications') + parent_post_comment=post_comment) PostCommentReplyNotification = get_post_comment_reply_notification_model() @@ -1253,44 +1257,28 @@ def reply_to_comment_for_post(self, post_comment, post, text): continue post_notification_target_user_is_post_comment_creator = post_notification_target_user.id == comment_creator post_notification_target_user_is_post_creator = post_notification_target_user.id == post_creator.id - post_notification_target_has_comment_reply_notifications_enabled = \ - post_notification_target_user.has_reply_notifications_enabled_for_post_comment( - post_comment=post_comment) - - if post_notification_target_has_comment_reply_notifications_enabled: - target_user_language_code = get_notification_language_code_for_target_user( - post_notification_target_user) - - with translation.override(target_user_language_code): - if post_notification_target_user_is_post_comment_creator: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s replied to your comment on a post.') % { - 'post_commenter_username': replier.username, - 'post_commenter_name': replier.profile.name, - }} - elif post_notification_target_user_is_post_creator: - notification_message = { - "en": _( - '%(post_commenter_name)s · %(post_commenter_username)s replied to a comment on your post.') % { - 'post_commenter_username': replier.username, - 'post_commenter_name': replier.profile.name, - }} - else: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s replied on a comment you also replied on.') % { - 'post_commenter_username': replier.username, - 'post_commenter_name': replier.profile.name, - }} - - self._send_post_comment_push_notification(post_comment=post_comment_reply, - notification_message=notification_message, - notification_target_user=post_notification_target_user) - - PostCommentReplyNotification.create_post_comment_reply_notification( - post_comment_id=post_comment_reply.pk, - owner_id=post_notification_target_user.id) + post_notification_target_user_is_post_subscriber = \ + post_notification_target_user.are_post_subscription_comment_notifications_enabled_for_post(post=post_comment.post) + + if post_notification_target_user_is_post_comment_creator or post_notification_target_user_is_post_creator: + send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, + target_user=post_notification_target_user) + PostCommentReplyNotification.create_post_comment_reply_notification( + post_comment_id=post_comment_reply.pk, + owner_id=post_notification_target_user.id) + elif post_notification_target_user_is_post_subscriber: + # send the post subscriber notification + send_post_notifications_subscription_comment_push_notification( + post_comment=post_comment_reply, + target_user=post_notification_target_user) + PostSubscriptionCommentNotification.create_post_subscription_comment_notification( + post_comment_id=post_comment_reply.pk, owner_id=post_notification_target_user.id) + else: + send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, + target_user=post_notification_target_user) + PostCommentReplyNotification.create_post_comment_reply_notification( + post_comment_id=post_comment_reply.pk, + owner_id=post_notification_target_user.id) return post_comment_reply @@ -2188,7 +2176,7 @@ def close_post(self, post): return post - def enable_post_notifications_for_post_with_id(self, post_id): + def enable_post_subscription_comment_notifications_for_post_with_id(self, post_id): Post = get_post_model() post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() @@ -2198,12 +2186,12 @@ def enable_post_notifications_for_post_with_id(self, post_id): post_notifications_subscription = PostNotificationsSubscription. \ get_or_create_post_notifications_subscription(post=post, subscriber=self) - post_notifications_subscription.post_notifications = True + post_notifications_subscription.comment_notifications = True post_notifications_subscription.save() return post - def disable_post_notifications_for_post_with_id(self, post_id): + def disable_post_subscription_comment_notifications_for_post_with_id(self, post_id): Post = get_post_model() post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() @@ -2213,14 +2201,14 @@ def disable_post_notifications_for_post_with_id(self, post_id): post_notifications_subscription = PostNotificationsSubscription.\ get_or_create_post_notifications_subscription(post=post, subscriber=self) - post_notifications_subscription.post_notifications = False + post_notifications_subscription.comment_notifications = False post_notifications_subscription.save() return post - def are_post_notifications_enabled_for_post(self, post): + def are_post_subscription_comment_notifications_enabled_for_post(self, post): PostNotificationsSubscription = get_post_notifications_subscription_model() - return PostNotificationsSubscription.are_post_notifications_enabled_for_user_with_username_and_post_with_id( + return PostNotificationsSubscription.are_comment_notifications_enabled_for_user_with_username_and_post_with_id( username=self.username, post_id=post.pk ) @@ -3319,11 +3307,6 @@ def _send_password_reset_email_with_token(self, password_reset_token): email.attach_alternative(html_content, 'text/html') email.send() - def _send_post_comment_push_notification(self, post_comment, notification_message, notification_target_user): - helpers.send_post_comment_push_notification_with_message(post_comment=post_comment, - message=notification_message, - target_user=notification_target_user) - def _delete_post_comment_notification(self, post_comment): if post_comment.parent_comment is not None: PostCommentNotification = get_post_comment_notification_model() @@ -3769,6 +3752,7 @@ class UserNotificationsSettings(models.Model): post_comment_user_mention_notifications = models.BooleanField(_('post comment user mention notifications'), default=True) post_user_mention_notifications = models.BooleanField(_('post user mention notifications'), default=True) + post_subscription_comment_notifications = models.BooleanField(_('post subscription comment notifications'), default=True) @classmethod def create_notifications_settings(cls, user): @@ -3785,7 +3769,8 @@ def update(self, post_comment_notifications=None, user_new_post_notifications=None, post_comment_user_mention_notifications=None, post_user_mention_notifications=None, - post_comment_reaction_notifications=None, ): + post_comment_reaction_notifications=None, + post_subscription_comment_notifications=None, ): if post_comment_notifications is not None: self.post_comment_notifications = post_comment_notifications @@ -3823,6 +3808,9 @@ def update(self, post_comment_notifications=None, if user_new_post_notifications is not None: self.user_new_post_notifications = user_new_post_notifications + if post_subscription_comment_notifications is not None: + self.post_subscription_comment_notifications = post_subscription_comment_notifications + self.save() diff --git a/openbook_notifications/helpers.py b/openbook_notifications/helpers.py index fc1c1a5c..ded20344 100644 --- a/openbook_notifications/helpers.py +++ b/openbook_notifications/helpers.py @@ -46,24 +46,149 @@ def send_post_reaction_push_notification(post_reaction): _send_notification_to_user(notification=one_signal_notification, user=post_creator) -def send_post_comment_push_notification_with_message(post_comment, message, target_user): +def send_post_comment_reply_push_notification(post_comment_reply, target_user): Notification = get_notification_model() + post_creator = post_comment_reply.post.creator + replier = post_comment_reply.commenter + post_comment = post_comment_reply.parent_comment + comment_creator_id = post_comment.commenter.id - notification_group = NOTIFICATION_GROUP_LOW_PRIORITY + target_user_is_post_comment_creator = target_user.id == comment_creator_id + target_user_is_post_creator = target_user.id == post_creator.id + target_has_comment_reply_notifications_enabled = \ + target_user.has_reply_notifications_enabled_for_post_comment( + post_comment=post_comment) - one_signal_notification = onesignal_sdk.Notification(post_body={ - "contents": message - }) + if target_has_comment_reply_notifications_enabled: + target_user_language_code = get_notification_language_code_for_target_user( + target_user) - notification_data = { - 'type': Notification.POST_COMMENT, - } + with translation.override(target_user_language_code): + if target_user_is_post_comment_creator: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s replied to your comment on a post.') % { + 'post_commenter_username': replier.username, + 'post_commenter_name': replier.profile.name, + }} + elif target_user_is_post_creator: + notification_message = { + "en": _( + '%(post_commenter_name)s · %(post_commenter_username)s replied to a comment on your post.') % { + 'post_commenter_username': replier.username, + 'post_commenter_name': replier.profile.name, + }} + else: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s replied on a comment you also replied on.') % { + 'post_commenter_username': replier.username, + 'post_commenter_name': replier.profile.name, + }} + + notification_group = NOTIFICATION_GROUP_LOW_PRIORITY - one_signal_notification.set_parameter('data', notification_data) - one_signal_notification.set_parameter('!thread_id', notification_group) - one_signal_notification.set_parameter('android_group', notification_group) + one_signal_notification = onesignal_sdk.Notification(post_body={ + "contents": notification_message + }) + + notification_data = { + 'type': Notification.POST_COMMENT, + } + + one_signal_notification.set_parameter('data', notification_data) + one_signal_notification.set_parameter('!thread_id', notification_group) + one_signal_notification.set_parameter('android_group', notification_group) + + _send_notification_to_user(notification=one_signal_notification, user=target_user) + + +def send_post_comment_push_notification(post_comment, target_user): + Notification = get_notification_model() + + post_creator = post_comment.post.creator + post_commenter = post_comment.commenter + + post_notification_target_user_is_post_creator = target_user.id == post_creator.id + post_notification_target_has_comment_notifications_enabled = \ + target_user.has_comment_notifications_enabled_for_post_with_id(post_id=post_comment.post_id) + + if post_notification_target_has_comment_notifications_enabled: + target_user_language_code = get_notification_language_code_for_target_user( + target_user) + with translation.override(target_user_language_code): + if post_notification_target_user_is_post_creator: + notification_message = { + "en": _('%(post_commenter_name)s · %(post_commenter_username)s commented on your post.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} + else: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you also commented on.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} + + notification_group = NOTIFICATION_GROUP_LOW_PRIORITY + + one_signal_notification = onesignal_sdk.Notification(post_body={ + "contents": notification_message + }) + + notification_data = { + 'type': Notification.POST_COMMENT, + } + + one_signal_notification.set_parameter('data', notification_data) + one_signal_notification.set_parameter('!thread_id', notification_group) + one_signal_notification.set_parameter('android_group', notification_group) + + _send_notification_to_user(notification=one_signal_notification, user=target_user) + + +def send_post_notifications_subscription_comment_push_notification(post_comment, target_user): + Notification = get_notification_model() + post_commenter = post_comment.commenter + + target_has_post_notifications_subscription_comment_notifications_enabled = \ + target_user.has_post_subscription_comment_notifications_enabled_for_post_with_id(post_id=post_comment.post_id) - _send_notification_to_user(notification=one_signal_notification, user=target_user) + if target_has_post_notifications_subscription_comment_notifications_enabled: + target_user_language_code = get_notification_language_code_for_target_user( + target_user) + with translation.override(target_user_language_code): + if post_comment.parent_comment is None: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you are subscribed to.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} + else: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s replied on a post you are subscribed to.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} + + notification_group = NOTIFICATION_GROUP_LOW_PRIORITY + + one_signal_notification = onesignal_sdk.Notification(post_body={ + "contents": notification_message + }) + + notification_data = { + 'type': Notification.POST_SUBSCRIPTION_COMMENT, + } + + one_signal_notification.set_parameter('data', notification_data) + one_signal_notification.set_parameter('!thread_id', notification_group) + one_signal_notification.set_parameter('android_group', notification_group) + + _send_notification_to_user(notification=one_signal_notification, user=target_user) def send_follow_push_notification(followed_user, following_user): From eee36c711de01bba1c982945b74958dc94d7d081 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:28:07 +0100 Subject: [PATCH 06/40] :sparkles: add model loaders n common serializer fields --- openbook_common/serializers_fields/post.py | 16 ++++++++++++++++ openbook_common/utils/model_loaders.py | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/openbook_common/serializers_fields/post.py b/openbook_common/serializers_fields/post.py index a8fdd34d..0ffe5260 100644 --- a/openbook_common/serializers_fields/post.py +++ b/openbook_common/serializers_fields/post.py @@ -157,3 +157,19 @@ def to_representation(self, post): is_encircled = post.is_encircled_post() return is_encircled + + +class PostSubscriptionCommentNotificationsEnabledField(Field): + def __init__(self, **kwargs): + kwargs['source'] = '*' + kwargs['read_only'] = True + super(PostSubscriptionCommentNotificationsEnabledField, self).__init__(**kwargs) + + def to_representation(self, post): + request = self.context.get('request') + request_user = request.user + + if request_user.is_anonymous: + return False + + return request_user.are_post_subscription_comment_notifications_enabled_for_post(post=post) diff --git a/openbook_common/utils/model_loaders.py b/openbook_common/utils/model_loaders.py index 70d7c070..69e8b305 100644 --- a/openbook_common/utils/model_loaders.py +++ b/openbook_common/utils/model_loaders.py @@ -133,6 +133,10 @@ def get_post_comment_notification_model(): return apps.get_model('openbook_notifications.PostCommentNotification') +def get_post_subscription_comment_notification_model(): + return apps.get_model('openbook_notifications.PostSubscriptionCommentNotification') + + def get_post_user_mention_notification_model(): return apps.get_model('openbook_notifications.PostUserMentionNotification') From 052076746b338c21c8a284723b342f02ec80d609 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:29:38 +0100 Subject: [PATCH 07/40] :sparkles: introduce PostSubscriptionCommentNotification model --- openbook_notifications/models/__init__.py | 1 + openbook_notifications/models/notification.py | 2 ++ .../post_subscription_comment_notification.py | 27 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 openbook_notifications/models/post_subscription_comment_notification.py diff --git a/openbook_notifications/models/__init__.py b/openbook_notifications/models/__init__.py index 39155c47..83162b7a 100644 --- a/openbook_notifications/models/__init__.py +++ b/openbook_notifications/models/__init__.py @@ -11,3 +11,4 @@ from .post_user_mention_notification import PostUserMentionNotification from .community_new_post_notification import CommunityNewPostNotification from .user_new_post_notification import UserNewPostNotification +from .post_subscription_comment_notification import PostSubscriptionCommentNotification diff --git a/openbook_notifications/models/notification.py b/openbook_notifications/models/notification.py index f7c67ffc..7ed618d9 100644 --- a/openbook_notifications/models/notification.py +++ b/openbook_notifications/models/notification.py @@ -23,6 +23,7 @@ class Notification(models.Model): POST_COMMENT_USER_MENTION = 'PCUM' COMMUNITY_NEW_POST = 'CNP' USER_NEW_POST = 'UNP' + POST_SUBSCRIPTION_COMMENT = 'PSC' NOTIFICATION_TYPES = ( (POST_REACTION, 'Post Reaction'), @@ -37,6 +38,7 @@ class Notification(models.Model): (POST_COMMENT_USER_MENTION, 'Post comment user mention'), (COMMUNITY_NEW_POST, 'New post in community'), (USER_NEW_POST, 'New post by user'), + (POST_SUBSCRIPTION_COMMENT, 'New comment on subscribed post'), ) notification_type = models.CharField(max_length=5, choices=NOTIFICATION_TYPES) diff --git a/openbook_notifications/models/post_subscription_comment_notification.py b/openbook_notifications/models/post_subscription_comment_notification.py new file mode 100644 index 00000000..ebca1ae7 --- /dev/null +++ b/openbook_notifications/models/post_subscription_comment_notification.py @@ -0,0 +1,27 @@ +from django.contrib.contenttypes.fields import GenericRelation +from django.db import models + +from openbook_notifications.models.notification import Notification +from openbook_posts.models import PostComment + + +class PostSubscriptionCommentNotification(models.Model): + notification = GenericRelation(Notification, related_name='post_subscription_comment_notifications') + post_comment = models.ForeignKey(PostComment, on_delete=models.CASCADE) + + @classmethod + def create_post_subscription_comment_notification(cls, post_comment_id, owner_id): + post_comment_notification = cls.objects.create(post_comment_id=post_comment_id) + Notification.create_notification(type=Notification.POST_SUBSCRIPTION_COMMENT, + content_object=post_comment_notification, + owner_id=owner_id) + return post_comment_notification + + @classmethod + def delete_post_subscription_comment_notification(cls, post_comment_id, owner_id): + cls.objects.filter(post_comment_id=post_comment_id, + notification__owner_id=owner_id).delete() + + @classmethod + def delete_post_subscription_comment_notifications(cls, post_comment_id): + cls.objects.filter(post_comment_id=post_comment_id).delete() From bbd46ffd301cdb626b211b0ae6814a5eaf6c10f0 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:31:05 +0100 Subject: [PATCH 08/40] :sparkles: add post comment notifications enabled field to seriazliers --- openbook_auth/views/auth/serializers.py | 2 ++ .../views/community/posts/serializers.py | 6 ++++-- openbook_hashtags/views/hashtag/serializers.py | 5 ++++- openbook_posts/views/post/serializers.py | 9 +++++---- openbook_posts/views/posts/serializers.py | 4 +++- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/openbook_auth/views/auth/serializers.py b/openbook_auth/views/auth/serializers.py index ce3c2230..6ea53dc6 100644 --- a/openbook_auth/views/auth/serializers.py +++ b/openbook_auth/views/auth/serializers.py @@ -70,6 +70,7 @@ class Meta: 'post_comment_reply_notifications', 'post_comment_user_mention_notifications', 'post_user_mention_notifications', + 'post_subscription_comment_notifications', ) @@ -86,6 +87,7 @@ class UpdateAuthenticatedUserNotificationsSettingsSerializer(serializers.Seriali post_comment_reply_notifications = serializers.BooleanField(required=False) post_comment_user_mention_notifications = serializers.BooleanField(required=False) post_user_mention_notifications = serializers.BooleanField(required=False) + post_subscription_comment_notifications = serializers.BooleanField(required=False) class RequestPasswordResetSerializer(serializers.Serializer): diff --git a/openbook_communities/views/community/posts/serializers.py b/openbook_communities/views/community/posts/serializers.py index 9c19345f..f3048add 100644 --- a/openbook_communities/views/community/posts/serializers.py +++ b/openbook_communities/views/community/posts/serializers.py @@ -6,7 +6,7 @@ CommonPostReactionSerializer, CommonPostLanguageSerializer, CommonHashtagSerializer from openbook_common.serializers_fields.community import CommunityPostsCountField from openbook_common.serializers_fields.post import PostReactionsEmojiCountField, CommentsCountField, PostCreatorField, \ - PostIsMutedField, ReactionField + PostIsMutedField, ReactionField, PostSubscriptionCommentNotificationsEnabledField from openbook_common.serializers_fields.request import RestrictedImageFileSizeField from openbook_communities.models import Community from openbook_communities.validators import community_name_characters_validator, community_name_exists @@ -52,6 +52,7 @@ class CommunityPostSerializer(serializers.ModelSerializer): reaction = ReactionField(reaction_serializer=CommonPostReactionSerializer) language = CommonPostLanguageSerializer() hashtags = CommonHashtagSerializer(many=True) + post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -75,7 +76,8 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', - 'hashtags' + 'hashtags', + 'post_subscription_comment_notifications_enabled' ) diff --git a/openbook_hashtags/views/hashtag/serializers.py b/openbook_hashtags/views/hashtag/serializers.py index b7c376de..ca06f2eb 100644 --- a/openbook_hashtags/views/hashtag/serializers.py +++ b/openbook_hashtags/views/hashtag/serializers.py @@ -7,7 +7,8 @@ CommonEmojiSerializer from openbook_common.serializers_fields.hashtag import HashtagPostsCountField, IsHashtagReportedField from openbook_common.serializers_fields.post import ReactionField, CommentsCountField, PostCreatorField, \ - PostReactionsEmojiCountField, PostIsMutedField, IsEncircledField, CirclesField + PostReactionsEmojiCountField, PostIsMutedField, IsEncircledField, CirclesField, \ + PostSubscriptionCommentNotificationsEnabledField from openbook_hashtags.models import Hashtag from openbook_hashtags.validators import hashtag_name_exists from openbook_posts.models import Post @@ -65,6 +66,7 @@ class GetHashtagPostsPostSerializer(serializers.ModelSerializer): hashtags = CommonHashtagSerializer(many=True) is_encircled = IsEncircledField() circles = CirclesField(circle_serializer=CommonCircleSerializer) + post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -87,6 +89,7 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', + 'post_subscription_comment_notifications_enabled', 'hashtags', 'circles' ) diff --git a/openbook_posts/views/post/serializers.py b/openbook_posts/views/post/serializers.py index 53c2d7a0..3ec783ac 100644 --- a/openbook_posts/views/post/serializers.py +++ b/openbook_posts/views/post/serializers.py @@ -6,11 +6,10 @@ from openbook_common.models import Badge, Language from openbook_common.serializers import CommonHashtagSerializer from openbook_common.serializers_fields.post import PostCreatorField, PostReactionsEmojiCountField, ReactionField, \ - CommentsCountField, CirclesField, PostIsMutedField, IsEncircledField + CommentsCountField, CirclesField, PostIsMutedField, IsEncircledField, PostSubscriptionCommentNotificationsEnabledField from openbook_communities.models import CommunityMembership, Community from openbook_communities.serializers_fields import CommunityMembershipsField from openbook_posts.models import PostImage, Post -from openbook_posts.serializer_fields import PostNotificationsEnabledField from openbook_posts.validators import post_uuid_exists, post_text_validators from openbook_posts.views.post_reaction.serializers import PostReactionSerializer @@ -154,6 +153,7 @@ class GetPostPostSerializer(serializers.ModelSerializer): language = PostLanguageSerializer() is_encircled = IsEncircledField() hashtags = CommonHashtagSerializer(many=True) + post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -175,6 +175,7 @@ class Meta: 'is_muted', 'is_edited', 'is_closed', + 'post_subscription_comment_notifications_enabled', 'is_encircled', 'media_height', 'media_width', @@ -299,14 +300,14 @@ class Meta: class SubscribeToPostNotificationsPostSerializer(serializers.ModelSerializer): - post_notifications_enabled = PostNotificationsEnabledField() + post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post fields = ( 'id', 'uuid', - 'post_notifications_enabled', + 'post_subscription_comment_notifications_enabled', ) diff --git a/openbook_posts/views/posts/serializers.py b/openbook_posts/views/posts/serializers.py index 75b94b4d..636433ac 100644 --- a/openbook_posts/views/posts/serializers.py +++ b/openbook_posts/views/posts/serializers.py @@ -8,7 +8,7 @@ from openbook_common.models import Emoji, Badge from openbook_common.serializers import CommonHashtagSerializer from openbook_common.serializers_fields.post import ReactionField, CommentsCountField, PostReactionsEmojiCountField, \ - CirclesField, PostCreatorField, PostIsMutedField, IsEncircledField + CirclesField, PostCreatorField, PostIsMutedField, IsEncircledField, PostSubscriptionCommentNotificationsEnabledField from openbook_common.serializers_fields.request import RestrictedImageFileSizeField, RestrictedFileSizeField from openbook_common.models import Language from openbook_communities.models import Community, CommunityMembership @@ -234,6 +234,7 @@ class AuthenticatedUserPostSerializer(serializers.ModelSerializer): language = PostLanguageSerializer() is_encircled = IsEncircledField() hashtags = CommonHashtagSerializer(many=True) + post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -258,6 +259,7 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', + 'post_subscription_comment_notifications_enabled', 'hashtags', ) From 109be6a8305a5b41e5d2b261150e4aec17e32537 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:32:46 +0100 Subject: [PATCH 09/40] :card_file_box: migrations for notification model, boolean name change --- ...post_subscription_comment_notifications.py | 18 +++++++++++++++ .../migrations/0018_auto_20200226_1514.py | 18 +++++++++++++++ ...019_postsubscriptioncommentnotification.py | 22 +++++++++++++++++++ .../migrations/0070_auto_20200226_1514.py | 18 +++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 openbook_auth/migrations/0052_usernotificationssettings_post_subscription_comment_notifications.py create mode 100644 openbook_notifications/migrations/0018_auto_20200226_1514.py create mode 100644 openbook_notifications/migrations/0019_postsubscriptioncommentnotification.py create mode 100644 openbook_posts/migrations/0070_auto_20200226_1514.py diff --git a/openbook_auth/migrations/0052_usernotificationssettings_post_subscription_comment_notifications.py b/openbook_auth/migrations/0052_usernotificationssettings_post_subscription_comment_notifications.py new file mode 100644 index 00000000..2c977e15 --- /dev/null +++ b/openbook_auth/migrations/0052_usernotificationssettings_post_subscription_comment_notifications.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.5 on 2020-02-26 14:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_auth', '0051_auto_20191209_1338'), + ] + + operations = [ + migrations.AddField( + model_name='usernotificationssettings', + name='post_subscription_comment_notifications', + field=models.BooleanField(default=True, verbose_name='post subscription comment notifications'), + ), + ] diff --git a/openbook_notifications/migrations/0018_auto_20200226_1514.py b/openbook_notifications/migrations/0018_auto_20200226_1514.py new file mode 100644 index 00000000..bb190cc5 --- /dev/null +++ b/openbook_notifications/migrations/0018_auto_20200226_1514.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.5 on 2020-02-26 14:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_notifications', '0017_auto_20191126_1809'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='notification_type', + field=models.CharField(choices=[('PR', 'Post Reaction'), ('PC', 'Post Comment'), ('PCR', 'Post Comment Reply'), ('PCRA', 'Post Comment Reaction'), ('CR', 'Connection Request'), ('CC', 'Connection Confirmed'), ('F', 'Follow'), ('CI', 'Community Invite'), ('PUM', 'Post user mention'), ('PCUM', 'Post comment user mention'), ('CNP', 'New post in community'), ('UNP', 'New post by user'), ('PSC', 'New comment on subscribed post')], max_length=5), + ), + ] diff --git a/openbook_notifications/migrations/0019_postsubscriptioncommentnotification.py b/openbook_notifications/migrations/0019_postsubscriptioncommentnotification.py new file mode 100644 index 00000000..0b9fb8a7 --- /dev/null +++ b/openbook_notifications/migrations/0019_postsubscriptioncommentnotification.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.5 on 2020-02-27 10:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_posts', '0070_auto_20200226_1514'), + ('openbook_notifications', '0018_auto_20200226_1514'), + ] + + operations = [ + migrations.CreateModel( + name='PostSubscriptionCommentNotification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('post_comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openbook_posts.PostComment')), + ], + ), + ] diff --git a/openbook_posts/migrations/0070_auto_20200226_1514.py b/openbook_posts/migrations/0070_auto_20200226_1514.py new file mode 100644 index 00000000..cd5b52da --- /dev/null +++ b/openbook_posts/migrations/0070_auto_20200226_1514.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.5 on 2020-02-26 14:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_posts', '0069_postnotificationssubscription'), + ] + + operations = [ + migrations.RenameField( + model_name='postnotificationssubscription', + old_name='post_notifications', + new_name='comment_notifications', + ), + ] From df74d2353e9100413f3333920fdb303628b94443 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:33:18 +0100 Subject: [PATCH 10/40] :art: rename methods in views --- openbook_auth/views/authenticated_user/views.py | 4 +++- openbook_posts/views/post/views.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openbook_auth/views/authenticated_user/views.py b/openbook_auth/views/authenticated_user/views.py index 585f1598..8c0bfdc5 100644 --- a/openbook_auth/views/authenticated_user/views.py +++ b/openbook_auth/views/authenticated_user/views.py @@ -112,6 +112,7 @@ def patch(self, request): post_comment_reply_notifications = data.get('post_comment_reply_notifications') post_comment_user_mention_notifications = data.get('post_comment_user_mention_notifications') post_user_mention_notifications = data.get('post_user_mention_notifications') + post_subscription_comment_notifications = data.get('post_subscription_comment_notifications') user = request.user @@ -128,7 +129,8 @@ def patch(self, request): post_comment_reaction_notifications=post_comment_reaction_notifications, post_comment_reply_notifications=post_comment_reply_notifications, post_comment_user_mention_notifications=post_comment_user_mention_notifications, - post_user_mention_notifications=post_user_mention_notifications + post_user_mention_notifications=post_user_mention_notifications, + post_subscription_comment_notifications=post_subscription_comment_notifications ) user_notifications_settings_serializer = AuthenticatedUserNotificationsSettingsSerializer( diff --git a/openbook_posts/views/post/views.py b/openbook_posts/views/post/views.py index 899ea9e9..be5f2b6a 100644 --- a/openbook_posts/views/post/views.py +++ b/openbook_posts/views/post/views.py @@ -338,7 +338,7 @@ def put(self, request, post_uuid): post_id = get_post_id_for_post_uuid(post_uuid) with transaction.atomic(): - post = user.enable_post_notifications_for_post_with_id(post_id=post_id) + post = user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) response_serializer = SubscribeToPostNotificationsPostSerializer(post, context={"request": request}) @@ -357,7 +357,7 @@ def delete(self, request, post_uuid): post_id = get_post_id_for_post_uuid(post_uuid) with transaction.atomic(): - post = user.disable_post_notifications_for_post_with_id(post_id=post_id) + post = user.disable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) response_serializer = SubscribeToPostNotificationsPostSerializer(post, context={"request": request}) From 7bbf166481fd6ddc8d0ca4a9688fee472f235cb8 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 27 Feb 2020 12:33:47 +0100 Subject: [PATCH 11/40] :white_check_mark: rename test mocks --- .../tests/views/test_authenticated_user.py | 7 +- .../tests/views/moderated_object.py | 93 ++++++++++++++++++- .../tests/views/test_post_comment_replies.py | 12 +-- .../tests/views/test_post_comments.py | 2 +- 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/openbook_auth/tests/views/test_authenticated_user.py b/openbook_auth/tests/views/test_authenticated_user.py index aba7dab8..7270a6e0 100644 --- a/openbook_auth/tests/views/test_authenticated_user.py +++ b/openbook_auth/tests/views/test_authenticated_user.py @@ -601,6 +601,7 @@ def test_can_update_notifications_settings(self): notifications_settings.post_comment_reaction_notifications = fake.boolean() notifications_settings.post_comment_user_mention_notifications = fake.boolean() notifications_settings.post_user_mention_notifications = fake.boolean() + notifications_settings.post_subscription_comment_notifications = fake.boolean() notifications_settings.save() @@ -618,6 +619,7 @@ def test_can_update_notifications_settings(self): new_post_comment_reply_notifications = not notifications_settings.post_comment_reply_notifications new_post_comment_user_mention_notifications = not notifications_settings.post_comment_user_mention_notifications new_post_user_mention_notifications = not notifications_settings.post_user_mention_notifications + new_post_subscription_comment_notifications = not notifications_settings.post_subscription_comment_notifications data = { 'post_comment_notifications': new_post_comment_notifications, @@ -631,7 +633,8 @@ def test_can_update_notifications_settings(self): 'post_comment_reply_notifications': new_post_comment_reply_notifications, 'post_comment_reaction_notifications': new_post_comment_reaction_notifications, 'post_comment_user_mention_notifications': new_post_comment_user_mention_notifications, - 'post_user_mention_notifications': new_post_user_mention_notifications + 'post_user_mention_notifications': new_post_user_mention_notifications, + 'post_subscription_comment_notifications': new_post_subscription_comment_notifications } url = self._get_url() @@ -655,6 +658,8 @@ def test_can_update_notifications_settings(self): new_post_comment_reply_notifications) self.assertEqual(notifications_settings.post_comment_reaction_notifications, new_post_comment_reaction_notifications) + self.assertEqual(notifications_settings.post_subscription_comment_notifications, + new_post_subscription_comment_notifications) def _get_url(self): return reverse('authenticated-user-notifications-settings') diff --git a/openbook_moderation/tests/views/moderated_object.py b/openbook_moderation/tests/views/moderated_object.py index 3a3ee897..94e5266e 100644 --- a/openbook_moderation/tests/views/moderated_object.py +++ b/openbook_moderation/tests/views/moderated_object.py @@ -20,7 +20,7 @@ get_post_comment_user_mention_notification_model, get_post_comment_user_mention_model, \ get_post_reaction_notification_model, get_post_user_mention_model, get_post_user_mention_notification_model, \ get_community_invite_notification_model, get_follow_notification_model, get_connection_request_notification_model, \ - get_connection_confirmed_notification_model + get_connection_confirmed_notification_model, get_post_subscription_comment_notification_model from openbook_communities.models import Community from openbook_moderation.models import ModeratedObject, ModeratedObjectDescriptionChangedLog, \ ModeratedObjectCategoryChangedLog, ModerationPenalty, ModerationCategory, ModeratedObjectStatusChangedLog, \ @@ -1431,6 +1431,49 @@ def test_approving_community_post_moderated_object_deletes_comment_notifications self.assertFalse(PostCommentNotification.objects.filter( post_comment=community_post_comment).exists()) + def test_approving_community_post_moderated_object_deletes_post_subscription_comment_notifications_for_comment(self): + """ + should delete post subscription comment notifications for comments on approving community_post moderated object + """ + global_moderator = make_global_moderator() + + community = make_community() + + community_post_creator = make_user() + community_post_commenter = make_user() + community_post_replier = make_user() + + community_post_creator.join_community_with_name(community_name=community.name) + community_post_commenter.join_community_with_name(community_name=community.name) + community_post_replier.join_community_with_name(community_name=community.name) + + community_post = community_post_creator.create_community_post( + community_name=community.name, + text=make_fake_post_text()) + community_post_comment = community_post_commenter.comment_post( + post=community_post, + text=make_fake_post_text()) + + reporter_community_post = make_user() + report_category = make_moderation_category() + + reporter_community_post.report_post(post=community_post, + category_id=report_category.pk) + + moderated_object = ModeratedObject.get_or_create_moderated_object_for_post( + post=community_post, + category_id=report_category.pk) + + url = self._get_url(moderated_object=moderated_object) + headers = make_authentication_headers_for_user(global_moderator) + response = self.client.post(url, **headers) + + PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(PostSubscriptionCommentNotification.objects.filter( + post_comment=community_post_comment).exists()) + def test_approving_community_post_moderated_object_deletes_comment_reply_notifications(self): """ should delete comment reply notifications on approving community_post moderated object @@ -1479,6 +1522,54 @@ def test_approving_community_post_moderated_object_deletes_comment_reply_notific self.assertFalse(PostCommentReplyNotification.objects.filter( post_comment=community_post_comment_reply).exists()) + def test_approving_community_post_moderated_object_deletes_post_subscription_comment_notifications_for_reply(self): + """ + should delete post subscription comment notifications for replies on approving community_post moderated object + """ + global_moderator = make_global_moderator() + + community = make_community() + + community_post_comment_creator = make_user() + community_post_replier = make_user() + + community_post_comment_creator.join_community_with_name(community_name=community.name) + community_post_replier.join_community_with_name(community_name=community.name) + + # create community post and comment + community_post = community_post_comment_creator.create_community_post( + community_name=community.name, + text=make_fake_post_text()) + community_post_comment = community_post_comment_creator.comment_post( + post=community_post, + text=make_fake_post_text()) + + # create reply + community_post_comment_reply = community_post_replier.reply_to_comment_for_post( + post_comment=community_post_comment, + post=community_post, + text=make_fake_post_comment_text()) + + reporter_community_post = make_user() + report_category = make_moderation_category() + + reporter_community_post.report_post(post=community_post, + category_id=report_category.pk) + + moderated_object = ModeratedObject.get_or_create_moderated_object_for_post( + post=community_post, + category_id=report_category.pk) + + url = self._get_url(moderated_object=moderated_object) + headers = make_authentication_headers_for_user(global_moderator) + response = self.client.post(url, **headers) + + PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(PostSubscriptionCommentNotification.objects.filter( + post_comment=community_post_comment_reply).exists()) + def test_approving_community_post_moderated_object_deletes_comment_reaction_notifications(self): """ should delete comment reaction notifications on approving community_post moderated object diff --git a/openbook_posts/tests/views/test_post_comment_replies.py b/openbook_posts/tests/views/test_post_comment_replies.py index b817849e..c3fec2a4 100644 --- a/openbook_posts/tests/views/test_post_comment_replies.py +++ b/openbook_posts/tests/views/test_post_comment_replies.py @@ -1496,7 +1496,7 @@ def test_comment_reply_in_an_encircled_post_with_a_user_removed_from_the_circle_ self.assertFalse(PostCommentReplyNotification.objects.filter(post_comment__text=reply_comment_text, notification__owner=foreign_user).exists()) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification_with_message') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(self, send_post_comment_reply_push_notification_call): """ @@ -1536,7 +1536,7 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(s target_user=post_creator ) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification_with_message') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creator(self, send_post_comment_reply_push_notification_call): """ @@ -1575,7 +1575,7 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creato target_user=post_creator ) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification_with_message') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_other_replier(self, send_post_comment_reply_push_notification_call): """ @@ -1615,7 +1615,7 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_other_repli target_user=foreign_user ) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification_with_message') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_muted(self, send_post_comment_reply_push_notification_call): """ @@ -1649,7 +1649,7 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_mut send_post_comment_reply_push_notification_call.assert_not_called() - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification_with_message') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_post_muted(self, send_post_comment_reply_push_notification_call): """ @@ -1716,7 +1716,7 @@ def test_replying_on_post_comment_doesnt_create_push_notification_when_user_bloc notification__owner=blocking_user).exists()) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification_with_message') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_replying_on_post_comment_doesnt_send_push_notification_when_user_blocked(self, send_post_comment_reply_push_notification_call): """ diff --git a/openbook_posts/tests/views/test_post_comments.py b/openbook_posts/tests/views/test_post_comments.py index c1405298..51f5b773 100644 --- a/openbook_posts/tests/views/test_post_comments.py +++ b/openbook_posts/tests/views/test_post_comments.py @@ -1470,7 +1470,7 @@ def test_commenting_in_post_does_not_create_notification_if_user_is_blocked(self self.assertFalse(PostCommentNotification.objects.filter(post_comment__text=post_comment_text, notification__owner=user).exists()) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification_with_message') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_commenting_in_post_does_not_send_push_notification_if_user_is_blocked(self, send_post_comment_push_notification_call): """ From 93fc686b7de0fd6a51a8dde1c9a620fc50c9c711 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Sun, 8 Mar 2020 20:20:29 +0100 Subject: [PATCH 12/40] :white_check_mark: fix tests, check notifications enabled before calling helpers --- openbook_auth/models.py | 45 +++- openbook_notifications/helpers.py | 195 ++++++++---------- .../tests/views/test_post_comment_replies.py | 25 +-- .../tests/views/test_post_comments.py | 1 - 4 files changed, 135 insertions(+), 131 deletions(-) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index b19b8d58..bda7d887 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -21,9 +21,6 @@ from openbook_auth.helpers import upload_to_user_cover_directory, upload_to_user_avatar_directory from openbook_hashtags.queries import make_search_hashtag_query_for_user_with_id, \ make_get_hashtag_with_name_for_user_with_id_query -from openbook_notifications.helpers import get_notification_language_code_for_target_user, \ - send_post_comment_push_notification, send_post_notifications_subscription_comment_push_notification, \ - send_post_comment_reply_push_notification from openbook_posts.queries import make_get_hashtag_posts_for_user_with_id_query from openbook_posts.query_collections import get_posts_for_user_collection from openbook_translation import translation_strategy @@ -1207,13 +1204,14 @@ def comment_post(self, post, text): if post_notification_target_user_is_post_creator: # send post notification - send_post_comment_push_notification(post_comment=post_comment, target_user=post_notification_target_user) + self._send_post_comment_push_notification(post_comment=post_comment, + target_user=post_notification_target_user) PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, owner_id=post_notification_target_user.id) elif post_notification_target_user_is_post_subscriber: # send the post subscriber notification - send_post_notifications_subscription_comment_push_notification( + self._send_post_notifications_subscription_comment_push_notification( post_comment=post_comment, target_user=post_notification_target_user) PostSubscriptionCommentNotification.create_post_subscription_comment_notification( @@ -1221,7 +1219,8 @@ def comment_post(self, post, text): else: # regular commenter - send_post_comment_push_notification(post_comment=post_comment, target_user=post_notification_target_user) + self._send_post_comment_push_notification(post_comment=post_comment, + target_user=post_notification_target_user) PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, owner_id=post_notification_target_user.id) @@ -1261,21 +1260,21 @@ def reply_to_comment_for_post(self, post_comment, post, text): post_notification_target_user.are_post_subscription_comment_notifications_enabled_for_post(post=post_comment.post) if post_notification_target_user_is_post_comment_creator or post_notification_target_user_is_post_creator: - send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, - target_user=post_notification_target_user) + self._send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, + target_user=post_notification_target_user) PostCommentReplyNotification.create_post_comment_reply_notification( post_comment_id=post_comment_reply.pk, owner_id=post_notification_target_user.id) elif post_notification_target_user_is_post_subscriber: # send the post subscriber notification - send_post_notifications_subscription_comment_push_notification( + self._send_post_notifications_subscription_comment_push_notification( post_comment=post_comment_reply, target_user=post_notification_target_user) PostSubscriptionCommentNotification.create_post_subscription_comment_notification( post_comment_id=post_comment_reply.pk, owner_id=post_notification_target_user.id) else: - send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, - target_user=post_notification_target_user) + self._send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, + target_user=post_notification_target_user) PostCommentReplyNotification.create_post_comment_reply_notification( post_comment_id=post_comment_reply.pk, owner_id=post_notification_target_user.id) @@ -3307,6 +3306,30 @@ def _send_password_reset_email_with_token(self, password_reset_token): email.attach_alternative(html_content, 'text/html') email.send() + def _send_post_notifications_subscription_comment_push_notification(self, post_comment, target_user): + target_has_post_notifications_subscription_comment_notifications_enabled = \ + target_user.has_post_subscription_comment_notifications_enabled_for_post_with_id( + post_id=post_comment.post_id) + + if target_has_post_notifications_subscription_comment_notifications_enabled: + helpers.send_post_notifications_subscription_comment_push_notification(post_comment=post_comment, + target_user=target_user) + + def _send_post_comment_reply_push_notification(self, post_comment_reply, target_user): + post_comment = post_comment_reply.parent_comment + target_has_comment_reply_notifications_enabled = \ + target_user.has_reply_notifications_enabled_for_post_comment(post_comment=post_comment) + if target_has_comment_reply_notifications_enabled: + helpers.send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, + target_user=target_user) + + def _send_post_comment_push_notification(self, post_comment, target_user): + post_notification_target_has_comment_notifications_enabled = \ + target_user.has_comment_notifications_enabled_for_post_with_id(post_id=post_comment.post_id) + if post_notification_target_has_comment_notifications_enabled: + helpers.send_post_comment_push_notification(post_comment=post_comment, + target_user=target_user) + def _delete_post_comment_notification(self, post_comment): if post_comment.parent_comment is not None: PostCommentNotification = get_post_comment_notification_model() diff --git a/openbook_notifications/helpers.py b/openbook_notifications/helpers.py index ded20344..4663b5a3 100644 --- a/openbook_notifications/helpers.py +++ b/openbook_notifications/helpers.py @@ -55,52 +55,47 @@ def send_post_comment_reply_push_notification(post_comment_reply, target_user): target_user_is_post_comment_creator = target_user.id == comment_creator_id target_user_is_post_creator = target_user.id == post_creator.id - target_has_comment_reply_notifications_enabled = \ - target_user.has_reply_notifications_enabled_for_post_comment( - post_comment=post_comment) + target_user_language_code = get_notification_language_code_for_target_user( + target_user) - if target_has_comment_reply_notifications_enabled: - target_user_language_code = get_notification_language_code_for_target_user( - target_user) + with translation.override(target_user_language_code): + if target_user_is_post_comment_creator: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s replied to your comment on a post.') % { + 'post_commenter_username': replier.username, + 'post_commenter_name': replier.profile.name, + }} + elif target_user_is_post_creator: + notification_message = { + "en": _( + '%(post_commenter_name)s · %(post_commenter_username)s replied to a comment on your post.') % { + 'post_commenter_username': replier.username, + 'post_commenter_name': replier.profile.name, + }} + else: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s replied on a comment you also replied on.') % { + 'post_commenter_username': replier.username, + 'post_commenter_name': replier.profile.name, + }} - with translation.override(target_user_language_code): - if target_user_is_post_comment_creator: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s replied to your comment on a post.') % { - 'post_commenter_username': replier.username, - 'post_commenter_name': replier.profile.name, - }} - elif target_user_is_post_creator: - notification_message = { - "en": _( - '%(post_commenter_name)s · %(post_commenter_username)s replied to a comment on your post.') % { - 'post_commenter_username': replier.username, - 'post_commenter_name': replier.profile.name, - }} - else: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s replied on a comment you also replied on.') % { - 'post_commenter_username': replier.username, - 'post_commenter_name': replier.profile.name, - }} - - notification_group = NOTIFICATION_GROUP_LOW_PRIORITY + notification_group = NOTIFICATION_GROUP_LOW_PRIORITY - one_signal_notification = onesignal_sdk.Notification(post_body={ - "contents": notification_message - }) + one_signal_notification = onesignal_sdk.Notification(post_body={ + "contents": notification_message + }) - notification_data = { - 'type': Notification.POST_COMMENT, - } + notification_data = { + 'type': Notification.POST_COMMENT, + } - one_signal_notification.set_parameter('data', notification_data) - one_signal_notification.set_parameter('!thread_id', notification_group) - one_signal_notification.set_parameter('android_group', notification_group) + one_signal_notification.set_parameter('data', notification_data) + one_signal_notification.set_parameter('!thread_id', notification_group) + one_signal_notification.set_parameter('android_group', notification_group) - _send_notification_to_user(notification=one_signal_notification, user=target_user) + _send_notification_to_user(notification=one_signal_notification, user=target_user) def send_post_comment_push_notification(post_comment, target_user): @@ -108,87 +103,79 @@ def send_post_comment_push_notification(post_comment, target_user): post_creator = post_comment.post.creator post_commenter = post_comment.commenter - post_notification_target_user_is_post_creator = target_user.id == post_creator.id - post_notification_target_has_comment_notifications_enabled = \ - target_user.has_comment_notifications_enabled_for_post_with_id(post_id=post_comment.post_id) + target_user_language_code = get_notification_language_code_for_target_user( + target_user) - if post_notification_target_has_comment_notifications_enabled: - target_user_language_code = get_notification_language_code_for_target_user( - target_user) - with translation.override(target_user_language_code): - if post_notification_target_user_is_post_creator: - notification_message = { - "en": _('%(post_commenter_name)s · %(post_commenter_username)s commented on your post.') % { - 'post_commenter_username': post_commenter.username, - 'post_commenter_name': post_commenter.profile.name, - }} - else: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you also commented on.') % { - 'post_commenter_username': post_commenter.username, - 'post_commenter_name': post_commenter.profile.name, - }} - - notification_group = NOTIFICATION_GROUP_LOW_PRIORITY + with translation.override(target_user_language_code): + if post_notification_target_user_is_post_creator: + notification_message = { + "en": _('%(post_commenter_name)s · %(post_commenter_username)s commented on your post.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} + else: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you also commented on.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} - one_signal_notification = onesignal_sdk.Notification(post_body={ - "contents": notification_message - }) + notification_group = NOTIFICATION_GROUP_LOW_PRIORITY - notification_data = { - 'type': Notification.POST_COMMENT, - } + one_signal_notification = onesignal_sdk.Notification(post_body={ + "contents": notification_message + }) - one_signal_notification.set_parameter('data', notification_data) - one_signal_notification.set_parameter('!thread_id', notification_group) - one_signal_notification.set_parameter('android_group', notification_group) + notification_data = { + 'type': Notification.POST_COMMENT, + } - _send_notification_to_user(notification=one_signal_notification, user=target_user) + one_signal_notification.set_parameter('data', notification_data) + one_signal_notification.set_parameter('!thread_id', notification_group) + one_signal_notification.set_parameter('android_group', notification_group) + + _send_notification_to_user(notification=one_signal_notification, user=target_user) def send_post_notifications_subscription_comment_push_notification(post_comment, target_user): Notification = get_notification_model() post_commenter = post_comment.commenter + target_user_language_code = get_notification_language_code_for_target_user( + target_user) - target_has_post_notifications_subscription_comment_notifications_enabled = \ - target_user.has_post_subscription_comment_notifications_enabled_for_post_with_id(post_id=post_comment.post_id) + with translation.override(target_user_language_code): + if post_comment.parent_comment is None: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you are subscribed to.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} + else: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s replied on a post you are subscribed to.') % { + 'post_commenter_username': post_commenter.username, + 'post_commenter_name': post_commenter.profile.name, + }} - if target_has_post_notifications_subscription_comment_notifications_enabled: - target_user_language_code = get_notification_language_code_for_target_user( - target_user) - with translation.override(target_user_language_code): - if post_comment.parent_comment is None: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you are subscribed to.') % { - 'post_commenter_username': post_commenter.username, - 'post_commenter_name': post_commenter.profile.name, - }} - else: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s replied on a post you are subscribed to.') % { - 'post_commenter_username': post_commenter.username, - 'post_commenter_name': post_commenter.profile.name, - }} - - notification_group = NOTIFICATION_GROUP_LOW_PRIORITY + notification_group = NOTIFICATION_GROUP_LOW_PRIORITY - one_signal_notification = onesignal_sdk.Notification(post_body={ - "contents": notification_message - }) + one_signal_notification = onesignal_sdk.Notification(post_body={ + "contents": notification_message + }) - notification_data = { - 'type': Notification.POST_SUBSCRIPTION_COMMENT, - } + notification_data = { + 'type': Notification.POST_SUBSCRIPTION_COMMENT, + } - one_signal_notification.set_parameter('data', notification_data) - one_signal_notification.set_parameter('!thread_id', notification_group) - one_signal_notification.set_parameter('android_group', notification_group) + one_signal_notification.set_parameter('data', notification_data) + one_signal_notification.set_parameter('!thread_id', notification_group) + one_signal_notification.set_parameter('android_group', notification_group) - _send_notification_to_user(notification=one_signal_notification, user=target_user) + _send_notification_to_user(notification=one_signal_notification, user=target_user) def send_follow_push_notification(followed_user, following_user): diff --git a/openbook_posts/tests/views/test_post_comment_replies.py b/openbook_posts/tests/views/test_post_comment_replies.py index c3fec2a4..bf7c7875 100644 --- a/openbook_posts/tests/views/test_post_comment_replies.py +++ b/openbook_posts/tests/views/test_post_comment_replies.py @@ -2,7 +2,6 @@ import json import random from unittest import mock -from unittest.mock import ANY from django.urls import reverse from faker import Faker @@ -1496,7 +1495,7 @@ def test_comment_reply_in_an_encircled_post_with_a_user_removed_from_the_circle_ self.assertFalse(PostCommentReplyNotification.objects.filter(post_comment__text=reply_comment_text, notification__owner=foreign_user).exists()) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(self, send_post_comment_reply_push_notification_call): """ @@ -1531,12 +1530,11 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(s parent_comment_id=post_comment.id) send_post_comment_reply_push_notification_call.assert_called_with( - post_comment=post_comment_reply, - message=ANY, + post_comment_reply=post_comment_reply, target_user=post_creator ) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creator(self, send_post_comment_reply_push_notification_call): """ @@ -1570,12 +1568,11 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creato parent_comment_id=post_comment.id) send_post_comment_reply_push_notification_call.assert_called_with( - post_comment=post_comment_reply, - message=ANY, + post_comment_reply=post_comment_reply, target_user=post_creator ) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_other_replier(self, send_post_comment_reply_push_notification_call): """ @@ -1610,12 +1607,11 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_other_repli parent_comment_id=post_comment.id) send_post_comment_reply_push_notification_call.assert_called_with( - post_comment=post_comment_reply, - message=ANY, + post_comment_reply=post_comment_reply, target_user=foreign_user ) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_muted(self, send_post_comment_reply_push_notification_call): """ @@ -1649,7 +1645,7 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_mut send_post_comment_reply_push_notification_call.assert_not_called() - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_post_muted(self, send_post_comment_reply_push_notification_call): """ @@ -1716,7 +1712,7 @@ def test_replying_on_post_comment_doesnt_create_push_notification_when_user_bloc notification__owner=blocking_user).exists()) - @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_post_comment_doesnt_send_push_notification_when_user_blocked(self, send_post_comment_reply_push_notification_call): """ @@ -1753,8 +1749,7 @@ def test_replying_on_post_comment_doesnt_send_push_notification_when_user_blocke # assert notification only for the post creator, not blocking user who blocked the commenting user send_post_comment_reply_push_notification_call.assert_called_once() send_post_comment_reply_push_notification_call.assert_called_with( - post_comment=post_comment_reply, - message=ANY, + post_comment_reply=post_comment_reply, target_user=post_creator ) diff --git a/openbook_posts/tests/views/test_post_comments.py b/openbook_posts/tests/views/test_post_comments.py index 51f5b773..ac8fec1b 100644 --- a/openbook_posts/tests/views/test_post_comments.py +++ b/openbook_posts/tests/views/test_post_comments.py @@ -1497,7 +1497,6 @@ def test_commenting_in_post_does_not_send_push_notification_if_user_is_blocked(s send_post_comment_push_notification_call.assert_called_once() send_post_comment_push_notification_call.assert_called_with( post_comment=post_comment, - message=ANY, target_user=foreign_user ) From 039fe50fe46e9eef65e5131cb81a52ca399eb1c4 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Mon, 9 Mar 2020 13:45:06 +0100 Subject: [PATCH 13/40] :white_check_mark: add tests for subscription notifications on comments n replies --- openbook_auth/models.py | 26 +- .../tests/views/test_post_comment_replies.py | 225 +++++++++++++++++- .../tests/views/test_post_comments.py | 207 +++++++++++++++- 3 files changed, 442 insertions(+), 16 deletions(-) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index bda7d887..b0387adb 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -1209,6 +1209,12 @@ def comment_post(self, post, text): PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, owner_id=post_notification_target_user.id) + elif not post_notification_target_user_is_post_subscriber: + # regular commenter + self._send_post_comment_push_notification(post_comment=post_comment, + target_user=post_notification_target_user) + PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, + owner_id=post_notification_target_user.id) elif post_notification_target_user_is_post_subscriber: # send the post subscriber notification self._send_post_notifications_subscription_comment_push_notification( @@ -1217,13 +1223,6 @@ def comment_post(self, post, text): PostSubscriptionCommentNotification.create_post_subscription_comment_notification( post_comment_id=post_comment.pk, owner_id=post_notification_target_user.id) - else: - # regular commenter - self._send_post_comment_push_notification(post_comment=post_comment, - target_user=post_notification_target_user) - PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, - owner_id=post_notification_target_user.id) - return post_comment def reply_to_comment_with_id_for_post_with_uuid(self, post_comment_id, post_uuid, text): @@ -1265,6 +1264,13 @@ def reply_to_comment_for_post(self, post_comment, post, text): PostCommentReplyNotification.create_post_comment_reply_notification( post_comment_id=post_comment_reply.pk, owner_id=post_notification_target_user.id) + elif not post_notification_target_user_is_post_subscriber: + self._send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, + target_user=post_notification_target_user) + PostCommentReplyNotification.create_post_comment_reply_notification( + post_comment_id=post_comment_reply.pk, + owner_id=post_notification_target_user.id) + elif post_notification_target_user_is_post_subscriber: # send the post subscriber notification self._send_post_notifications_subscription_comment_push_notification( @@ -1272,12 +1278,6 @@ def reply_to_comment_for_post(self, post_comment, post, text): target_user=post_notification_target_user) PostSubscriptionCommentNotification.create_post_subscription_comment_notification( post_comment_id=post_comment_reply.pk, owner_id=post_notification_target_user.id) - else: - self._send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, - target_user=post_notification_target_user) - PostCommentReplyNotification.create_post_comment_reply_notification( - post_comment_id=post_comment_reply.pk, - owner_id=post_notification_target_user.id) return post_comment_reply diff --git a/openbook_posts/tests/views/test_post_comment_replies.py b/openbook_posts/tests/views/test_post_comment_replies.py index bf7c7875..abcdc60d 100644 --- a/openbook_posts/tests/views/test_post_comment_replies.py +++ b/openbook_posts/tests/views/test_post_comment_replies.py @@ -12,7 +12,7 @@ from openbook_common.tests.helpers import make_authentication_headers_for_user, make_fake_post_text, \ make_fake_post_comment_text, make_user, make_circle, make_community, make_private_community -from openbook_notifications.models import PostCommentReplyNotification +from openbook_notifications.models import PostCommentReplyNotification, PostSubscriptionCommentNotification from openbook_posts.models import PostComment, PostCommentUserMention logger = logging.getLogger(__name__) @@ -1711,7 +1711,6 @@ def test_replying_on_post_comment_doesnt_create_push_notification_when_user_bloc self.assertFalse(PostCommentReplyNotification.objects.filter(post_comment__text=reply_comment_text, notification__owner=blocking_user).exists()) - @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_post_comment_doesnt_send_push_notification_when_user_blocked(self, send_post_comment_reply_push_notification_call): @@ -2079,6 +2078,228 @@ def test_should_retrieve_comment_replies_slice_with_sort_for_min_id_and_max_id(s self.assertTrue(len(replies_after_min_id) == count_min) self.assertTrue(len(replies_before_max_id) == count_max) + # post notifications subscription tests + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_foreign_user_replying_on_post_comment_sends_push_notification_to_subscriber(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should send a push notification to the subscriber when someone replies on a post comment + """ + post_subscriber = make_user() + post_creator = make_user() + foreign_user = make_user() + headers = make_authentication_headers_for_user(foreign_user) + + post = post_creator.create_public_post(text=make_fake_post_text()) + + post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + reply_comment_text = make_fake_post_comment_text() + data = self._get_create_post_comment_request_data(reply_comment_text) + + # subscribe to post comment notifications + post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + post_comment_reply = PostComment.objects.get( + commenter_id=foreign_user.pk, + parent_comment_id=post_comment.id) + + send_post_notifications_subscription_comment_push_notification_call.assert_called_with( + post_comment=post_comment_reply, + target_user=post_subscriber + ) + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_foreign_user_replying_on_post_comment_doesnt_send_push_notification_to_subscriber_if_muted(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should NOT send a push notification to the subscriber when someone replies on a post comment if post is muted + """ + post_subscriber = make_user() + post_creator = make_user() + foreign_user = make_user() + headers = make_authentication_headers_for_user(foreign_user) + + post = post_creator.create_public_post(text=make_fake_post_text()) + + post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + reply_comment_text = make_fake_post_comment_text() + data = self._get_create_post_comment_request_data(reply_comment_text) + + # subscribe to post comment notifications and mute + post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post_subscriber.mute_post(post=post) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_replying_on_foreign_post_comment_does_not_send_subscription_notification_to_a_subscribed_commenter(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should NOT send post subscription push notification to the commenter when replying on a foreign post comment + """ + user = make_user() + headers = make_authentication_headers_for_user(user) + + post_creator = make_user() + commenter = make_user() + foreign_user = make_user() + + post = post_creator.create_public_post(text=make_fake_post_text()) + post_comment = commenter.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + + foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, + post_uuid=post.uuid, + text=make_fake_post_comment_text()) + + # subscribe to post comment notifications + commenter.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + reply_comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(reply_comment_text) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + + def test_replying_on_post_comment_doesnt_create_post_subscription_push_notification_when_user_blocked(self): + """ + should NOT create notification when a blocked user replies on a foreign users post comment, on a post + that you are subscribed to + """ + blocked_user = make_user() + headers = make_authentication_headers_for_user(blocked_user) + + blocking_user_aka_subscriber = make_user() + + post_creator = make_user() + + post = post_creator.create_public_post(text=make_fake_post_text()) + post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + + # Block user + blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) + # subscribe to post comment notifications + blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + reply_comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(reply_comment_text) + + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=reply_comment_text, + notification__owner=blocking_user_aka_subscriber).exists()) + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_replying_on_post_comment_doesnt_send_post_subscription_push_notification_when_user_blocked(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should NOT send push notification to blocking user when the blocked user replies on a post the + blocking user is subscribed to + """ + blocked_user = make_user() + headers = make_authentication_headers_for_user(blocked_user) + + post_creator = make_user() + blocking_user_aka_subscriber = make_user() + + post = post_creator.create_public_post(text=make_fake_post_text()) + post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + + # Block user + blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) + # subscribe to post comment notifications + blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + reply_comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(reply_comment_text) + + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + # assert notification only for the post creator, not blocking user who blocked the commenting user + send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + + def test_reply_in_community_post_does_not_create_foreign_user_post_subscription_notification_when_closed(self): + """ + should NOT create a post subscription notification when a creator comments reply in a CLOSED community post where a foreign user is subscribed + """ + post_creator = make_user() + admin = make_user() + community = make_community(creator=admin) + post_creator.join_community_with_name(community_name=community.name) + post = post_creator.create_community_post(text=make_fake_post_text(), community_name=community.name) + + foreign_user = make_user() + foreign_user.join_community_with_name(community_name=community.name) + post_comment = post_creator.comment_post(post=post, text=make_fake_post_text()) + + # subscribe to notifications + foreign_user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + # post will be closed now + post.is_closed = True + post.save() + + reply_comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(reply_comment_text) + headers = make_authentication_headers_for_user(post_creator) + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=reply_comment_text, + notification__owner=foreign_user).exists()) + + def test_reply_in_community_post_by_admin_does_create_subscription_notification_to_another_admin_when_closed(self): + """ + should create a post subscription notification to a admin when another admin comments reply in a CLOSED post + """ + post_creator = make_user() + admin = make_user() + community = make_community(creator=admin) + admin_two = make_user() + admin_two.join_community_with_name(community_name=community.name) + admin.add_administrator_with_username_to_community_with_name(username=admin_two.username, + community_name=community.name) + post_creator.join_community_with_name(community_name=community.name) + post = post_creator.create_community_post(text=make_fake_post_text(), community_name=community.name) + + post_comment = post_creator.comment_post(post=post, text=make_fake_post_text()) + # subscribe to notifications + admin.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + # post will be closed now + post.is_closed = True + post.save() + + reply_comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(reply_comment_text) + headers = make_authentication_headers_for_user(admin_two) + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + self.assertTrue(PostSubscriptionCommentNotification.objects.filter(post_comment__text=reply_comment_text, + notification__owner=admin).exists()) + def _get_create_post_comment_request_data(self, reply_comment_text): return { 'text': reply_comment_text diff --git a/openbook_posts/tests/views/test_post_comments.py b/openbook_posts/tests/views/test_post_comments.py index ac8fec1b..fb2cdde4 100644 --- a/openbook_posts/tests/views/test_post_comments.py +++ b/openbook_posts/tests/views/test_post_comments.py @@ -16,7 +16,7 @@ from openbook_hashtags.models import Hashtag from openbook_moderation.models import ModeratedObject from openbook_notifications.models import PostCommentNotification, PostCommentReplyNotification, \ - PostCommentUserMentionNotification, Notification + PostCommentUserMentionNotification, Notification, PostSubscriptionCommentNotification from openbook_posts.models import PostComment, PostCommentUserMention, Post logger = logging.getLogger(__name__) @@ -2056,6 +2056,211 @@ def test_can_retrieve_moderated_pending_community_post_comments(self): for post_id in post_comments_ids: self.assertIn(post_id, response_post_comments_ids) + # post notifications subscription tests + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_foreign_user_commenting_on_post_comment_sends_push_notification_to_subscriber(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should send a push notification to the subscriber when someone comments on a post + """ + post_subscriber = make_user() + post_creator = make_user() + foreign_user = make_user() + headers = make_authentication_headers_for_user(foreign_user) + + post = post_creator.create_public_post(text=make_fake_post_text()) + comment_text = make_fake_post_comment_text() + data = self._get_create_post_comment_request_data(comment_text) + + # subscribe to post comment notifications + post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + url = self._get_url(post) + self.client.put(url, data, **headers) + + post_comment = PostComment.objects.get( + commenter_id=foreign_user.pk, + post_id=post.id + ) + + send_post_notifications_subscription_comment_push_notification_call.assert_called_with( + post_comment=post_comment, + target_user=post_subscriber + ) + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_foreign_user_commenting_on_post_comment_doesnt_send_push_notification_to_subscriber_if_muted(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should NOT send a push notification to the subscriber when someone comments on a post if post is muted + """ + post_subscriber = make_user() + post_creator = make_user() + foreign_user = make_user() + headers = make_authentication_headers_for_user(foreign_user) + + post = post_creator.create_public_post(text=make_fake_post_text()) + comment_text = make_fake_post_comment_text() + data = self._get_create_post_comment_request_data(comment_text) + + # subscribe to post comment notifications and mute + post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post_subscriber.mute_post(post=post) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + url = self._get_url(post) + self.client.put(url, data, **headers) + + send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_commenting_on_post_does_not_send_subscription_notification_to_the_subscribed_post_creator(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should NOT send post subscription push notification to the post creator when commenting on a post + """ + user = make_user() + headers = make_authentication_headers_for_user(user) + + post_creator = make_user() + post = post_creator.create_public_post(text=make_fake_post_text()) + + # subscribe to post comment notifications + post_creator.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + comment_text = make_fake_post_comment_text() + data = self._get_create_post_comment_request_data(comment_text) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + url = self._get_url(post) + self.client.put(url, data, **headers) + + send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + + def test_commenting_on_post_doesnt_create_post_subscription_push_notification_when_user_blocked(self): + """ + should NOT create notification when a blocked user comments on a foreign users post, on a post + that you are subscribed to + """ + blocked_user = make_user() + headers = make_authentication_headers_for_user(blocked_user) + + blocking_user_aka_subscriber = make_user() + + post_creator = make_user() + + post = post_creator.create_public_post(text=make_fake_post_text()) + + # Block user + blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) + # subscribe to post comment notifications + blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(comment_text) + + url = self._get_url(post) + self.client.put(url, data, **headers) + + self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=comment_text, + notification__owner=blocking_user_aka_subscriber).exists()) + + @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + def test_commenting_on_post_doesnt_send_post_subscription_push_notification_when_user_blocked(self, + send_post_notifications_subscription_comment_push_notification_call): + """ + should NOT send push notification to blocking user when the blocked user comments on a post the + blocking user is subscribed to + """ + blocked_user = make_user() + headers = make_authentication_headers_for_user(blocked_user) + + post_creator = make_user() + blocking_user_aka_subscriber = make_user() + + post = post_creator.create_public_post(text=make_fake_post_text()) + + # Block user + blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) + # subscribe to post comment notifications + blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + send_post_notifications_subscription_comment_push_notification_call.reset_mock() + + comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(comment_text) + + url = self._get_url(post) + self.client.put(url, data, **headers) + + send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + + def test_comment_in_community_post_does_not_create_foreign_user_post_subscription_notification_when_closed(self): + """ + should NOT create a post subscription notification when a creator comments in a CLOSED community post where a foreign user is subscribed + """ + post_creator = make_user() + admin = make_user() + community = make_community(creator=admin) + post_creator.join_community_with_name(community_name=community.name) + post = post_creator.create_community_post(text=make_fake_post_text(), community_name=community.name) + + foreign_user = make_user() + foreign_user.join_community_with_name(community_name=community.name) + + # subscribe to notifications + foreign_user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + # post will be closed now + post.is_closed = True + post.save() + + comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(comment_text) + headers = make_authentication_headers_for_user(post_creator) + url = self._get_url(post) + self.client.put(url, data, **headers) + + self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=comment_text, + notification__owner=foreign_user).exists()) + + def test_comment_in_community_post_by_admin_does_create_subscription_notification_to_another_admin_when_closed(self): + """ + should create a post subscription notification to a admin when another admin comments in a CLOSED post + """ + post_creator = make_user() + admin = make_user() + community = make_community(creator=admin) + admin_two = make_user() + admin_two.join_community_with_name(community_name=community.name) + admin.add_administrator_with_username_to_community_with_name(username=admin_two.username, + community_name=community.name) + post_creator.join_community_with_name(community_name=community.name) + post = post_creator.create_community_post(text=make_fake_post_text(), community_name=community.name) + + # subscribe to notifications + admin.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + # post will be closed now + post.is_closed = True + post.save() + + comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(comment_text) + headers = make_authentication_headers_for_user(admin_two) + url = self._get_url(post) + self.client.put(url, data, **headers) + + self.assertTrue(PostSubscriptionCommentNotification.objects.filter(post_comment__text=comment_text, + notification__owner=admin).exists()) + def _get_create_post_comment_request_data(self, post_comment_text): return { 'text': post_comment_text From 395b3a0eb7429dd6204d62ff6f7a74aea5e317f7 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Mon, 9 Mar 2020 15:55:21 +0100 Subject: [PATCH 14/40] :recycle: rename api class --- openbook/urls.py | 5 +++-- openbook_posts/views/post/serializers.py | 4 ++-- openbook_posts/views/post/views.py | 12 ++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/openbook/urls.py b/openbook/urls.py index c943cfca..22d95946 100644 --- a/openbook/urls.py +++ b/openbook/urls.py @@ -73,7 +73,7 @@ UnreadNotificationsCount from openbook_posts.views.post.views import PostItem, PostOpen, PostClose, MutePost, UnmutePost, TranslatePost, \ PostPreviewLinkData, SearchPostParticipants, GetPostParticipants, PublishPost, PostStatus, \ - SubscribeToPostNotifications + SubscribePostSubscriptionCommentNotifications from openbook_posts.views.post_comment.post_comment_reaction.views import PostCommentReactionItem from openbook_posts.views.post_comment.post_comment_reactions.views import PostCommentReactions, \ PostCommentReactionsEmojiCount @@ -160,7 +160,8 @@ post_notifications_patterns = [ path('mute/', MutePost.as_view(), name='mute-post'), path('unmute/', UnmutePost.as_view(), name='unmute-post'), - path('subscribe/', SubscribeToPostNotifications.as_view(), name='subscribe-post'), + path('subscribe/comments/', SubscribePostSubscriptionCommentNotifications.as_view(), + name='subscribe-post-subscription-comment-notifications'), ] post_comment_reactions_patterns = [ diff --git a/openbook_posts/views/post/serializers.py b/openbook_posts/views/post/serializers.py index 3ec783ac..4d94f680 100644 --- a/openbook_posts/views/post/serializers.py +++ b/openbook_posts/views/post/serializers.py @@ -215,7 +215,7 @@ class ClosePostSerializer(serializers.Serializer): ) -class SubscribeToPostNotificationsSerializer(serializers.Serializer): +class SubscribePostSubscriptionCommentNotificationsSerializer(serializers.Serializer): post_uuid = serializers.UUIDField( validators=[post_uuid_exists], required=True, @@ -299,7 +299,7 @@ class Meta: ) -class SubscribeToPostNotificationsPostSerializer(serializers.ModelSerializer): +class SubscribePostSubscriptionCommentNotificationsPostSerializer(serializers.ModelSerializer): post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: diff --git a/openbook_posts/views/post/views.py b/openbook_posts/views/post/views.py index be5f2b6a..40413afa 100644 --- a/openbook_posts/views/post/views.py +++ b/openbook_posts/views/post/views.py @@ -14,7 +14,7 @@ OpenClosePostSerializer, \ OpenPostSerializer, ClosePostSerializer, TranslatePostSerializer, \ SearchPostParticipantsSerializer, PostParticipantSerializer, GetPostParticipantsSerializer, PublishPostSerializer, \ - GetPostStatusSerializer, SubscribeToPostNotificationsSerializer, SubscribeToPostNotificationsPostSerializer + GetPostStatusSerializer, SubscribePostSubscriptionCommentNotificationsSerializer, SubscribePostSubscriptionCommentNotificationsPostSerializer from openbook_translation.strategies.base import TranslationClientError, UnsupportedLanguagePairException, \ MaxTextLengthExceededError @@ -322,14 +322,14 @@ def get(self, request, post_uuid): return Response(preview_link_data, status=status.HTTP_200_OK) -class SubscribeToPostNotifications(APIView): +class SubscribePostSubscriptionCommentNotifications(APIView): permission_classes = (IsAuthenticated,) def put(self, request, post_uuid): request_data = request.data.copy() request_data['post_uuid'] = post_uuid - serializer = SubscribeToPostNotificationsSerializer(data=request_data) + serializer = SubscribePostSubscriptionCommentNotificationsSerializer(data=request_data) serializer.is_valid(raise_exception=True) user = request.user @@ -340,7 +340,7 @@ def put(self, request, post_uuid): with transaction.atomic(): post = user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) - response_serializer = SubscribeToPostNotificationsPostSerializer(post, context={"request": request}) + response_serializer = SubscribePostSubscriptionCommentNotificationsPostSerializer(post, context={"request": request}) return Response(response_serializer.data, status=status.HTTP_201_CREATED) @@ -348,7 +348,7 @@ def delete(self, request, post_uuid): request_data = request.data.copy() request_data['post_uuid'] = post_uuid - serializer = SubscribeToPostNotificationsSerializer(data=request_data) + serializer = SubscribePostSubscriptionCommentNotificationsSerializer(data=request_data) serializer.is_valid(raise_exception=True) data = serializer.validated_data @@ -359,6 +359,6 @@ def delete(self, request, post_uuid): with transaction.atomic(): post = user.disable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) - response_serializer = SubscribeToPostNotificationsPostSerializer(post, context={"request": request}) + response_serializer = SubscribePostSubscriptionCommentNotificationsPostSerializer(post, context={"request": request}) return Response(response_serializer.data, status=status.HTTP_200_OK) From b65021942b67a1861ee27fb9593dedd2962b1362 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 10 Mar 2020 14:45:06 +0100 Subject: [PATCH 15/40] :white_check_mark: add tests for subscribing/unsub to post notifications for comments --- openbook_auth/checkers.py | 9 +- openbook_auth/models.py | 4 +- openbook_posts/tests/views/test_post.py | 456 ++++++++++++++++++++++++ 3 files changed, 465 insertions(+), 4 deletions(-) diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index adbb36a2..0908025b 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -343,7 +343,12 @@ def check_can_disable_new_post_notifications_for_community(user, community): ) -def check_can_disable_post_notifications_for_post(user, post): +def check_can_disable_post_notifications_subscription_for_post(user, post): + if not user.can_see_post(post=post): + raise ValidationError( + _('You do not have permissions to view this post.'), + ) + PostNotificationsSubscription = get_post_notifications_subscription_model() post_notifications_enabled = \ PostNotificationsSubscription.is_user_with_username_subscribed_to_comment_notifications_for_post_with_id( @@ -355,7 +360,7 @@ def check_can_disable_post_notifications_for_post(user, post): ) -def check_can_enable_post_notifications_for_post(user, post): +def check_can_enable_post_notifications_subscription_for_post(user, post): if not user.can_see_post(post=post): raise ValidationError( _('You do not have permissions to view this post.'), diff --git a/openbook_auth/models.py b/openbook_auth/models.py index b0387adb..965c152d 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -2180,7 +2180,7 @@ def enable_post_subscription_comment_notifications_for_post_with_id(self, post_i post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() - check_can_enable_post_notifications_for_post(user=self, post=post) + check_can_enable_post_notifications_subscription_for_post(user=self, post=post) post_notifications_subscription = PostNotificationsSubscription. \ get_or_create_post_notifications_subscription(post=post, subscriber=self) @@ -2195,7 +2195,7 @@ def disable_post_subscription_comment_notifications_for_post_with_id(self, post_ post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() - check_can_disable_post_notifications_for_post(user=self, post=post) + check_can_disable_post_notifications_subscription_for_post(user=self, post=post) post_notifications_subscription = PostNotificationsSubscription.\ get_or_create_post_notifications_subscription(post=post, subscriber=self) diff --git a/openbook_posts/tests/views/test_post.py b/openbook_posts/tests/views/test_post.py index 1aebf7d8..cea9e24a 100644 --- a/openbook_posts/tests/views/test_post.py +++ b/openbook_posts/tests/views/test_post.py @@ -1304,6 +1304,462 @@ def _get_url(self, post): }) +class SubscribePostSubscriptionCommentNotificationsAPITests(OpenbookAPITestCase): + """ + SubscribePostSubscriptionCommentNotifications API + """ + + fixtures = [ + 'openbook_circles/fixtures/circles.json' + ] + + def test_can_subscribe_comment_notifications_for_public_post(self): + """ + should be able to subscribe to comment notifications for public post and return 200 + """ + user = make_user() + subscriber = make_user() + headers = make_authentication_headers_for_user(subscriber) + post = user.create_public_post(text=make_fake_post_text()) + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(subscriber.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_cannot_subscribe_to_comment_notifications_for_post_if_encircled_post(self): + """ + should NOT be able to subscribe to comment notifications for post if encircled post + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_subscribe_to_comment_notifications_for_post_if_part_of_encircled_post(self): + """ + should be able to subscribe to comment notifications for post if part of encircled post + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) + user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_subscribe_to_comment_notifications_for_community_post_if_public(self): + """ + should be able to subscribe to comment notifications for community post if public + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + + headers = make_authentication_headers_for_user(user) + user.join_community_with_name(community_name=community.name) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_cannot_subscribe_to_comment_notifications_for_closed_community_post_if_public(self): + """ + should NOT be able to subscribe to comment notifications for closed community post + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post.is_closed = True + post.save() + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_creator(self): + """ + should be able to subscribe to comment notifications for closed post if post creator in community + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post.is_closed = True + post.save() + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_subscribe_to_comment_notifications_for_closed_community_post_administrator(self): + """ + should be able to subscribe to comment notifications for closed post if administrator in community + """ + user = make_user() + + admin = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(admin) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post.is_closed = True + post.save() + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(admin.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_moderator(self): + """ + should be able to subscribe to comment notifications for closed post if moderator in community + """ + user = make_user() + + admin = make_user() + moderator = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + moderator.join_community_with_name(community_name=community.name) + admin.add_moderator_with_username_to_community_with_name(username=moderator.username, + community_name=community.name) + + headers = make_authentication_headers_for_user(moderator) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post.is_closed = True + post.save() + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(moderator.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_cannot_subscribe_to_comment_notifications_for_community_post_if_private_and_not_member(self): + """ + should NOT be able to subscribe to comment notifications for private community post and not a member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_subscribe_to_comment_notifications_for_community_post_if_private_and_member(self): + """ + should be able to subscribe to comment notifications for private community post if member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + + foreign_user.invite_user_with_username_to_community_with_name(username=user.username, + community_name=community.name) + + user.join_community_with_name(community_name=community.name) + + url = self._get_url(post) + + response = self.client.put(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_unsubscribe_comment_notifications_for_public_post(self): + """ + should be able to unsubscribe to comment notifications for public post and return 200 + """ + user = make_user() + subscriber = make_user() + headers = make_authentication_headers_for_user(subscriber) + post = user.create_public_post(text=make_fake_post_text()) + + subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_cannot_unsubscribe_to_comment_notifications_for_post_if_encircled_post(self): + """ + should NOT be able to unsubscribe to comment notifications for post if encircled post + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_unsubscribe_to_comment_notifications_for_post_if_part_of_encircled_post(self): + """ + should be able to unsubscribe to comment notifications for post if part of encircled post + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) + user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) + user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_unsubscribe_to_comment_notifications_for_community_post_if_public(self): + """ + should be able to unsubscribe to comment notifications for community post if public + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + + headers = make_authentication_headers_for_user(user) + user.join_community_with_name(community_name=community.name) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_cannot_unsubscribe_to_comment_notifications_for_closed_community_post_if_public(self): + """ + should NOT be able to unsubscribe to comment notifications for closed community post + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + post.is_closed = True + post.save() + + url = self._get_url(post) + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_creator(self): + """ + should be able to unsubscribe to comment notifications for closed post if post creator in community + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post.is_closed = True + post.save() + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_administrator(self): + """ + should be able to unsubscribe to comment notifications for closed post if administrator in community + """ + user = make_user() + + admin = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(admin) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + admin.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post.is_closed = True + post.save() + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(admin.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_moderator(self): + """ + should be able to unsubscribe to comment notifications for closed post if moderator in community + """ + user = make_user() + + admin = make_user() + moderator = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + moderator.join_community_with_name(community_name=community.name) + admin.add_moderator_with_username_to_community_with_name(username=moderator.username, + community_name=community.name) + + headers = make_authentication_headers_for_user(moderator) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + moderator.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post.is_closed = True + post.save() + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(moderator.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_cannot_unsubscribe_to_comment_notifications_for_community_post_if_private_and_not_member(self): + """ + should NOT be able to unsubscribe to comment notifications for private community post if not a member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def test_can_unsubscribe_to_comment_notifications_for_community_post_if_private_and_member(self): + """ + should be able to unsubscribe to comment notifications for private community post if member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + + foreign_user.invite_user_with_username_to_community_with_name(username=user.username, + community_name=community.name) + + user.join_community_with_name(community_name=community.name) + user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + + url = self._get_url(post) + + response = self.client.delete(url, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + + def _get_url(self, post): + return reverse('subscribe-post-subscription-comment-notifications', kwargs={ + 'post_uuid': post.uuid + }) + + class MutePostAPITests(OpenbookAPITestCase): """ MutePostAPI From b95a2ae92049eb100e3c0f5dddbc8b37b65fef3b Mon Sep 17 00:00:00 2001 From: Shantanu Date: Fri, 13 Mar 2020 18:47:45 +0100 Subject: [PATCH 16/40] :recycle: change endpoint for new design --- openbook/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openbook/urls.py b/openbook/urls.py index 22d95946..4d050e21 100644 --- a/openbook/urls.py +++ b/openbook/urls.py @@ -73,7 +73,7 @@ UnreadNotificationsCount from openbook_posts.views.post.views import PostItem, PostOpen, PostClose, MutePost, UnmutePost, TranslatePost, \ PostPreviewLinkData, SearchPostParticipants, GetPostParticipants, PublishPost, PostStatus, \ - SubscribePostSubscriptionCommentNotifications + PostNotificationsSubscriptionSettings from openbook_posts.views.post_comment.post_comment_reaction.views import PostCommentReactionItem from openbook_posts.views.post_comment.post_comment_reactions.views import PostCommentReactions, \ PostCommentReactionsEmojiCount @@ -160,8 +160,8 @@ post_notifications_patterns = [ path('mute/', MutePost.as_view(), name='mute-post'), path('unmute/', UnmutePost.as_view(), name='unmute-post'), - path('subscribe/comments/', SubscribePostSubscriptionCommentNotifications.as_view(), - name='subscribe-post-subscription-comment-notifications'), + path('subscribe/', PostNotificationsSubscriptionSettings.as_view(), + name='post-notifications-subscription-settings'), ] post_comment_reactions_patterns = [ From f529ba53a44ddb8c3f5aadc90aed7e71c9a18796 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Fri, 13 Mar 2020 18:49:05 +0100 Subject: [PATCH 17/40] :sparkles: new PostNotificationsSubscription and PostCommentNotificationsSubscription model --- .../migrations/0071_auto_20200313_1648.py | 54 ++++++++ openbook_posts/models.py | 118 ++++++++++++++++-- 2 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 openbook_posts/migrations/0071_auto_20200313_1648.py diff --git a/openbook_posts/migrations/0071_auto_20200313_1648.py b/openbook_posts/migrations/0071_auto_20200313_1648.py new file mode 100644 index 00000000..f64803d0 --- /dev/null +++ b/openbook_posts/migrations/0071_auto_20200313_1648.py @@ -0,0 +1,54 @@ +# Generated by Django 2.2.5 on 2020-03-13 15:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('openbook_posts', '0070_auto_20200226_1514'), + ] + + operations = [ + migrations.AddField( + model_name='postnotificationssubscription', + name='comment_reaction_notifications', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='postnotificationssubscription', + name='reaction_notifications', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='postnotificationssubscription', + name='reply_notifications', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='postnotificationssubscription', + name='reply_where_commented_notifications', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='postnotificationssubscription', + name='comment_notifications', + field=models.BooleanField(default=True), + ), + migrations.CreateModel( + name='PostCommentNotificationsSubscription', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reaction_notifications', models.BooleanField(default=True)), + ('reply_notifications', models.BooleanField(default=True)), + ('post_comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications_subscriptions', to='openbook_posts.PostComment')), + ('subscriber', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='post_comment_notifications_subscriptions', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('post_comment', 'subscriber')}, + }, + ), + ] diff --git a/openbook_posts/models.py b/openbook_posts/models.py index 88d0d0d6..c3e3aeae 100644 --- a/openbook_posts/models.py +++ b/openbook_posts/models.py @@ -1318,36 +1318,116 @@ def create_post_comment_user_mention(cls, user, post_comment): return post_comment_user_mention +class PostCommentNotificationsSubscription(models.Model): + subscriber = models.ForeignKey(User, + on_delete=models.CASCADE, + related_name='post_comment_notifications_subscriptions', + null=False, + blank=False) + post_comment = models.ForeignKey(PostComment, on_delete=models.CASCADE, related_name='notifications_subscriptions', + null=False, blank=False) + reaction_notifications = models.BooleanField(default=True, blank=False) + reply_notifications = models.BooleanField(default=True, blank=False) + + class Meta: + unique_together = ('post_comment', 'subscriber',) + + @classmethod + def create_post_comment_notifications_subscription(cls, subscriber, post_comment): + if not cls.objects.filter(subscriber=subscriber, post_comment=post_comment).exists(): + return cls.objects.create(subscriber=subscriber, post_comment=post_comment) + + post_comment_notifications_subscription = cls.objects.get(subscriber=subscriber, post_comment=post_comment) + post_comment_notifications_subscription.save() + + return post_comment_notifications_subscription + + @classmethod + def get_or_create_post_comment_notifications_subscription(cls, subscriber, post_comment): + try: + post_comment_notifications_subscription = cls.objects.get(subscriber_id=subscriber.pk, + post_comment_id=post_comment.pk) + except cls.DoesNotExist: + post_comment_notifications_subscription = cls.create_post_comment_notifications_subscription( + subscriber=subscriber, + post_comment=post_comment + ) + + return post_comment_notifications_subscription + + @classmethod + def delete_post_comment_notifications_subscription(cls, subscriber, post_comment): + return cls.objects.filter(subscriber=subscriber, post_comment=post_comment).delete() + + @classmethod + def post_comment_notifications_subscription_exists(cls, subscriber, post_comment): + return cls.objects.filter(subscriber=subscriber, post_comment=post_comment).exists() + + @classmethod + def is_user_with_username_subscribed_to_reply_notifications_for_post_comment_with_id(cls, username, post_comment_id): + return cls.objects.filter(post_comment_id=post_comment_id, + subscriber__username=username, + comment_notifications=True).exists() + + @classmethod + def are_reply_notifications_enabled_for_user_with_username_and_post_comment_with_id(cls, username, post_comment_id): + return cls.objects.filter(post_comment_id=post_comment_id, + subscriber__username=username, + comment_notifications=True).exists() + + class PostNotificationsSubscription(models.Model): subscriber = models.ForeignKey(User, on_delete=models.CASCADE, related_name='post_notifications_subscriptions', null=False, blank=False) post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='notifications_subscriptions', null=False, blank=False) - comment_notifications = models.BooleanField(default=False, blank=False) + reaction_notifications = models.BooleanField(default=True, blank=False) + comment_reaction_notifications = models.BooleanField(default=True, blank=False) + comment_notifications = models.BooleanField(default=True, blank=False) + reply_notifications = models.BooleanField(default=False, blank=False) + reply_where_commented_notifications = models.BooleanField(default=True, blank=False) class Meta: unique_together = ('post', 'subscriber',) @classmethod - def create_post_notifications_subscription(cls, subscriber, post): + def create_post_notifications_subscription(cls, subscriber, post, + comment_notifications=None, + comment_reaction_notifications=None, + reaction_notifications=None, + reply_notifications=None, + reply_where_commented_notifications=None): if not cls.objects.filter(subscriber=subscriber, post=post).exists(): - return cls.objects.create(subscriber=subscriber, post=post) + return cls.objects.create(subscriber=subscriber, + post=post, + comment_notifications=comment_notifications, + comment_reaction_notifications=comment_reaction_notifications, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications, + reply_where_commented_notifications=reply_where_commented_notifications) post_notifications_subscription = cls.objects.get(subscriber=subscriber, post=post) - post_notifications_subscription.save() - return post_notifications_subscription @classmethod - def get_or_create_post_notifications_subscription(cls, subscriber, post): + def get_or_create_post_notifications_subscription(cls, subscriber, post, comment_notifications=None, + comment_reaction_notifications=None, + reaction_notifications=None, + reply_notifications=None, + reply_where_commented_notifications=None): try: post_notifications_subscription = cls.objects.get(subscriber_id=subscriber.pk, post_id=post.pk) except cls.DoesNotExist: post_notifications_subscription = cls.create_post_notifications_subscription( subscriber=subscriber, - post=post + post=post, + comment_notifications=comment_notifications, + comment_reaction_notifications=comment_reaction_notifications, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications, + reply_where_commented_notifications=reply_where_commented_notifications ) return post_notifications_subscription @@ -1371,3 +1451,27 @@ def are_comment_notifications_enabled_for_user_with_username_and_post_with_id(cl return cls.objects.filter(post_id=post_id, subscriber__username=username, comment_notifications=True).exists() + + def update(self, + comment_notifications=None, + comment_reaction_notifications=None, + reaction_notifications=None, + reply_notifications=None, + reply_where_commented_notifications=None): + + if comment_notifications is not None: + self.comment_notifications = comment_notifications + + if comment_reaction_notifications is not None: + self.comment_reaction_notifications = comment_reaction_notifications + + if reaction_notifications is not None: + self.reaction_notifications = reaction_notifications + + if reply_notifications is not None: + self.reply_notifications = reply_notifications + + if reply_where_commented_notifications is not None: + self.reply_where_commented_notifications = reply_where_commented_notifications + + self.save() From 56e7082d1c591881399c34e08a1b5dca59584535 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Fri, 13 Mar 2020 18:49:41 +0100 Subject: [PATCH 18/40] :recycle: refactor views with new design --- openbook_auth/checkers.py | 10 +--- openbook_auth/models.py | 65 ++++++++++++++++------ openbook_posts/views/post/serializers.py | 26 ++++++++- openbook_posts/views/post/views.py | 70 ++++++++++++++++++++---- 4 files changed, 131 insertions(+), 40 deletions(-) diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index 0908025b..b47da547 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -360,20 +360,12 @@ def check_can_disable_post_notifications_subscription_for_post(user, post): ) -def check_can_enable_post_notifications_subscription_for_post(user, post): +def check_can_create_or_update_post_notifications_subscription_for_post(user, post): if not user.can_see_post(post=post): raise ValidationError( _('You do not have permissions to view this post.'), ) - PostNotificationsSubscription = get_post_notifications_subscription_model() - post_notifications_enabled = \ - PostNotificationsSubscription.is_user_with_username_subscribed_to_comment_notifications_for_post_with_id( - username=user.username, post_id=post.pk) - - if post_notifications_enabled: - raise ValidationError('Post notifications are already enabled.') - def check_can_get_community_with_name_members(user, community_name): check_is_not_banned_from_community_with_name(user=user, community_name=community_name) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 965c152d..79f912d8 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -2175,35 +2175,68 @@ def close_post(self, post): return post - def enable_post_subscription_comment_notifications_for_post_with_id(self, post_id): + def create_post_notifications_subscription_for_post_with_id(self, post_id, + comment_notifications=None, + comment_reaction_notifications=None, + reaction_notifications=None, + reply_notifications=None, + reply_where_commented_notifications=None): Post = get_post_model() post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() - check_can_enable_post_notifications_subscription_for_post(user=self, post=post) + check_can_create_or_update_post_notifications_subscription_for_post(user=self, post=post) - post_notifications_subscription = PostNotificationsSubscription. \ - get_or_create_post_notifications_subscription(post=post, subscriber=self) - - post_notifications_subscription.comment_notifications = True - post_notifications_subscription.save() + post_notifications_subscription = PostNotificationsSubscription.\ + create_post_notifications_subscription( + post=post, + subscriber=self, + comment_notifications=comment_notifications, + comment_reaction_notifications=comment_reaction_notifications, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications, + reply_where_commented_notifications=reply_where_commented_notifications + ) - return post + return post_notifications_subscription - def disable_post_subscription_comment_notifications_for_post_with_id(self, post_id): + def update_post_notifications_subscription_for_post_with_id(self, post_id, + comment_notifications=None, + comment_reaction_notifications=None, + reaction_notifications=None, + reply_notifications=None, + reply_where_commented_notifications=None): Post = get_post_model() post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() - check_can_disable_post_notifications_subscription_for_post(user=self, post=post) + check_can_create_or_update_post_notifications_subscription_for_post(user=self, post=post) - post_notifications_subscription = PostNotificationsSubscription.\ - get_or_create_post_notifications_subscription(post=post, subscriber=self) - - post_notifications_subscription.comment_notifications = False - post_notifications_subscription.save() + post_notifications_subscription = PostNotificationsSubscription.objects.get(post=post, subscriber=self) + post_notifications_subscription.update( + comment_notifications=comment_notifications, + comment_reaction_notifications=comment_reaction_notifications, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications, + reply_where_commented_notifications=reply_where_commented_notifications + ) - return post + return post_notifications_subscription + + # def disable_post_subscription_comment_notifications_for_post_with_id(self, post_id): + # Post = get_post_model() + # post = Post.objects.get(id=post_id) + # PostNotificationsSubscription = get_post_notifications_subscription_model() + # + # check_can_disable_post_notifications_subscription_for_post(user=self, post=post) + # + # post_notifications_subscription = PostNotificationsSubscription.\ + # get_or_create_post_notifications_subscription(post=post, subscriber=self) + # + # post_notifications_subscription.comment_notifications = False + # post_notifications_subscription.save() + # + # return post def are_post_subscription_comment_notifications_enabled_for_post(self, post): PostNotificationsSubscription = get_post_notifications_subscription_model() diff --git a/openbook_posts/views/post/serializers.py b/openbook_posts/views/post/serializers.py index 4d94f680..57fe36d8 100644 --- a/openbook_posts/views/post/serializers.py +++ b/openbook_posts/views/post/serializers.py @@ -9,7 +9,7 @@ CommentsCountField, CirclesField, PostIsMutedField, IsEncircledField, PostSubscriptionCommentNotificationsEnabledField from openbook_communities.models import CommunityMembership, Community from openbook_communities.serializers_fields import CommunityMembershipsField -from openbook_posts.models import PostImage, Post +from openbook_posts.models import PostImage, Post, PostNotificationsSubscription from openbook_posts.validators import post_uuid_exists, post_text_validators from openbook_posts.views.post_reaction.serializers import PostReactionSerializer @@ -215,11 +215,16 @@ class ClosePostSerializer(serializers.Serializer): ) -class SubscribePostSubscriptionCommentNotificationsSerializer(serializers.Serializer): +class PostNotificationsSubscriptionSettingsSerializer(serializers.Serializer): post_uuid = serializers.UUIDField( validators=[post_uuid_exists], required=True, ) + comment_notifications = serializers.BooleanField(required=False) + comment_reaction_notifications = serializers.BooleanField(required=False) + reaction_notifications = serializers.BooleanField(required=False) + reply_notifications = serializers.BooleanField(required=False) + reply_where_commented_notifications = serializers.BooleanField(required=False) class OpenPostSerializer(serializers.Serializer): @@ -299,7 +304,7 @@ class Meta: ) -class SubscribePostSubscriptionCommentNotificationsPostSerializer(serializers.ModelSerializer): +class PostNotificationsSubscriptionSettingsPostSerializer(serializers.ModelSerializer): post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: @@ -311,6 +316,21 @@ class Meta: ) +class PostNotificationsSubscriptionSettingsResponseSerializer(serializers.ModelSerializer): + + class Meta: + model = PostNotificationsSubscription + fields = ( + 'id', + 'post', + 'comment_notifications', + 'comment_reaction_notifications', + 'reaction_notifications', + 'reply_notifications', + 'reply_where_commented_notifications', + ) + + class PublishPostSerializer(serializers.Serializer): post_uuid = serializers.UUIDField( validators=[post_uuid_exists], diff --git a/openbook_posts/views/post/views.py b/openbook_posts/views/post/views.py index 40413afa..50396f47 100644 --- a/openbook_posts/views/post/views.py +++ b/openbook_posts/views/post/views.py @@ -14,7 +14,8 @@ OpenClosePostSerializer, \ OpenPostSerializer, ClosePostSerializer, TranslatePostSerializer, \ SearchPostParticipantsSerializer, PostParticipantSerializer, GetPostParticipantsSerializer, PublishPostSerializer, \ - GetPostStatusSerializer, SubscribePostSubscriptionCommentNotificationsSerializer, SubscribePostSubscriptionCommentNotificationsPostSerializer + GetPostStatusSerializer, PostNotificationsSubscriptionSettingsSerializer, \ + PostNotificationsSubscriptionSettingsPostSerializer, PostNotificationsSubscriptionSettingsResponseSerializer from openbook_translation.strategies.base import TranslationClientError, UnsupportedLanguagePairException, \ MaxTextLengthExceededError @@ -322,43 +323,88 @@ def get(self, request, post_uuid): return Response(preview_link_data, status=status.HTTP_200_OK) -class SubscribePostSubscriptionCommentNotifications(APIView): +class PostNotificationsSubscriptionSettings(APIView): permission_classes = (IsAuthenticated,) def put(self, request, post_uuid): request_data = request.data.copy() request_data['post_uuid'] = post_uuid - serializer = SubscribePostSubscriptionCommentNotificationsSerializer(data=request_data) + serializer = PostNotificationsSubscriptionSettingsSerializer(data=request_data) serializer.is_valid(raise_exception=True) user = request.user data = serializer.validated_data post_uuid = data.get('post_uuid') + comment_notifications = data.get('comment_notifications') + comment_reaction_notifications = data.get('comment_reaction_notifications') + reaction_notifications = data.get('reaction_notifications') + reply_notifications = data.get('reply_notifications') + reply_where_commented_notifications = data.get('reply_where_commented_notifications') post_id = get_post_id_for_post_uuid(post_uuid) with transaction.atomic(): - post = user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) - - response_serializer = SubscribePostSubscriptionCommentNotificationsPostSerializer(post, context={"request": request}) + post_notifications_subscription = user.create_post_notifications_subscription_for_post_with_id( + post_id=post_id, + comment_notifications=comment_notifications, + comment_reaction_notifications=comment_reaction_notifications, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications, + reply_where_commented_notifications=reply_where_commented_notifications + ) + + response_serializer = PostNotificationsSubscriptionSettingsResponseSerializer(post_notifications_subscription, + context={"request": request}) return Response(response_serializer.data, status=status.HTTP_201_CREATED) - def delete(self, request, post_uuid): + def patch(self, request, post_uuid): request_data = request.data.copy() request_data['post_uuid'] = post_uuid - serializer = SubscribePostSubscriptionCommentNotificationsSerializer(data=request_data) + serializer = PostNotificationsSubscriptionSettingsSerializer(data=request_data) serializer.is_valid(raise_exception=True) - data = serializer.validated_data user = request.user + data = serializer.validated_data post_uuid = data.get('post_uuid') + comment_notifications = data.get('comment_notifications') + comment_reaction_notifications = data.get('comment_reaction_notifications') + reaction_notifications = data.get('reaction_notifications') + reply_notifications = data.get('reply_notifications') + reply_where_commented_notifications = data.get('reply_where_commented_notifications') post_id = get_post_id_for_post_uuid(post_uuid) with transaction.atomic(): - post = user.disable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) - - response_serializer = SubscribePostSubscriptionCommentNotificationsPostSerializer(post, context={"request": request}) + post_notifications_subscription = user.update_post_notifications_subscription_for_post_with_id( + post_id=post_id, + comment_notifications=comment_notifications, + comment_reaction_notifications=comment_reaction_notifications, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications, + reply_where_commented_notifications=reply_where_commented_notifications + ) + + response_serializer = PostNotificationsSubscriptionSettingsResponseSerializer(post_notifications_subscription, + context={"request": request}) return Response(response_serializer.data, status=status.HTTP_200_OK) + + # def delete(self, request, post_uuid): + # request_data = request.data.copy() + # request_data['post_uuid'] = post_uuid + # + # serializer = PostNotificationsSubscriptionSettingsSerializer(data=request_data) + # serializer.is_valid(raise_exception=True) + # data = serializer.validated_data + # + # user = request.user + # post_uuid = data.get('post_uuid') + # post_id = get_post_id_for_post_uuid(post_uuid) + # + # with transaction.atomic(): + # post = user.disable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) + # + # response_serializer = PostNotificationsSubscriptionSettingsPostSerializer(post, context={"request": request}) + # + # return Response(response_serializer.data, status=status.HTTP_200_OK) From 339830a1362953a9960de7fba158b8f2f079db39 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 24 Mar 2020 15:34:54 +0100 Subject: [PATCH 19/40] :fire: remove model not needed --- ...ete_postsubscriptioncommentnotification.py | 16 +++++++++++ .../post_subscription_comment_notification.py | 27 ------------------- 2 files changed, 16 insertions(+), 27 deletions(-) create mode 100644 openbook_notifications/migrations/0020_delete_postsubscriptioncommentnotification.py delete mode 100644 openbook_notifications/models/post_subscription_comment_notification.py diff --git a/openbook_notifications/migrations/0020_delete_postsubscriptioncommentnotification.py b/openbook_notifications/migrations/0020_delete_postsubscriptioncommentnotification.py new file mode 100644 index 00000000..3eb798c7 --- /dev/null +++ b/openbook_notifications/migrations/0020_delete_postsubscriptioncommentnotification.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.5 on 2020-03-16 17:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_notifications', '0019_postsubscriptioncommentnotification'), + ] + + operations = [ + migrations.DeleteModel( + name='PostSubscriptionCommentNotification', + ), + ] diff --git a/openbook_notifications/models/post_subscription_comment_notification.py b/openbook_notifications/models/post_subscription_comment_notification.py deleted file mode 100644 index ebca1ae7..00000000 --- a/openbook_notifications/models/post_subscription_comment_notification.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.contrib.contenttypes.fields import GenericRelation -from django.db import models - -from openbook_notifications.models.notification import Notification -from openbook_posts.models import PostComment - - -class PostSubscriptionCommentNotification(models.Model): - notification = GenericRelation(Notification, related_name='post_subscription_comment_notifications') - post_comment = models.ForeignKey(PostComment, on_delete=models.CASCADE) - - @classmethod - def create_post_subscription_comment_notification(cls, post_comment_id, owner_id): - post_comment_notification = cls.objects.create(post_comment_id=post_comment_id) - Notification.create_notification(type=Notification.POST_SUBSCRIPTION_COMMENT, - content_object=post_comment_notification, - owner_id=owner_id) - return post_comment_notification - - @classmethod - def delete_post_subscription_comment_notification(cls, post_comment_id, owner_id): - cls.objects.filter(post_comment_id=post_comment_id, - notification__owner_id=owner_id).delete() - - @classmethod - def delete_post_subscription_comment_notifications(cls, post_comment_id): - cls.objects.filter(post_comment_id=post_comment_id).delete() From 20a1a981ae573540ff56130aec8b8773eba6b977 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 24 Mar 2020 15:36:35 +0100 Subject: [PATCH 20/40] :recycle: refactor methods with new design --- openbook_auth/checkers.py | 7 + openbook_auth/models.py | 242 ++++++++++++--------- openbook_common/serializers_fields/post.py | 15 -- openbook_common/utils/model_loaders.py | 8 +- openbook_notifications/helpers.py | 52 ++--- openbook_notifications/models/__init__.py | 1 - openbook_posts/models.py | 119 +++++----- 7 files changed, 226 insertions(+), 218 deletions(-) diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index b47da547..4a01ea2b 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -367,6 +367,13 @@ def check_can_create_or_update_post_notifications_subscription_for_post(user, po ) +def check_can_create_or_update_notifications_subscription_for_post_comment(user, post_comment): + if not user.can_see_post_comment(post_comment=post_comment): + raise ValidationError( + _('You do not have permissions to view this post comment.'), + ) + + def check_can_get_community_with_name_members(user, community_name): check_is_not_banned_from_community_with_name(user=user, community_name=community_name) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 79f912d8..7aaa2b06 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -36,7 +36,7 @@ get_moderation_penalty_model, get_post_comment_mute_model, get_post_comment_reaction_model, \ get_post_comment_reaction_notification_model, get_top_post_model, get_top_post_community_exclusion_model, \ get_hashtag_model, get_profile_posts_community_exclusion_model, get_user_new_post_notification_model, \ - get_post_subscription_comment_notification_model + get_post_comment_notifications_subscription_model from openbook_common.validators import name_characters_validator from openbook_notifications import helpers from openbook_auth.checkers import * @@ -671,6 +671,22 @@ def has_muted_post_with_id(self, post_id): def has_muted_post_comment_with_id(self, post_comment_id): return self.post_comment_mutes.filter(post_comment_id=post_comment_id).exists() + def has_disabled_comment_notifications_for_post(self, post_id): + return self.post_notifications_subscriptions.filter(post_id=post_id, comment_notifications=False).exists() + + def has_disabled_reply_notifications_for_post(self, post_id, post_comment_id): + has_replied = self.has_replied_on_comment_with_id(post_comment_id=post_comment_id) + is_commenter = self.posts_comments.filter(id=post_comment_id).exists() + if has_replied or is_commenter: + return self.post_notifications_subscriptions.filter(post_id=post_id, + reply_where_commented_notifications=False).exists() + else: + return self.post_notifications_subscriptions.filter(post_id=post_id, reply_notifications=False).exists() + + def has_disabled_reply_notifications_for_post_comment(self, post_comment_id): + return self.post_comment_notifications_subscriptions.filter(post_comment_id=post_comment_id, + reply_notifications=False).exists() + def has_blocked_user_with_id(self, user_id): return self.user_blocks.filter(blocked_user_id=user_id).exists() @@ -774,6 +790,12 @@ def has_reacted_to_post_comment_with_id(self, post_comment_id, emoji_id=None): def has_commented_post_with_id(self, post_id): return self.posts_comments.filter(post_id=post_id).exists() + def has_replied_on_post_with_id(self, post_id): + return self.posts_comments.filter(parent_comment__isnull=False, post_id=post_id).exists() + + def has_replied_on_comment_with_id(self, post_comment_id): + return self.posts_comments.filter(parent_comment_id=post_comment_id).exists() + def has_notification_with_id(self, notification_id): return self.notifications.filter(pk=notification_id).exists() @@ -799,17 +821,15 @@ def has_reaction_notifications_enabled_for_post_comment(self, post_comment): post_comment_id=post_comment.id) def has_comment_notifications_enabled_for_post_with_id(self, post_id): - return self.notifications_settings.post_comment_notifications and not self.has_muted_post_with_id( - post_id=post_id) - - def has_post_subscription_comment_notifications_enabled_for_post_with_id(self, post_id): - return self.notifications_settings.post_subscription_comment_notifications and not \ - self.has_muted_post_with_id(post_id=post_id) + return self.notifications_settings.post_comment_notifications and \ + not self.has_disabled_comment_notifications_for_post(post_id=post_id) def has_reply_notifications_enabled_for_post_comment(self, post_comment): - return self.notifications_settings.post_comment_reply_notifications and not self.has_muted_post_with_id( - post_id=post_comment.post_id) and not self.has_muted_post_comment_with_id( - post_comment_id=post_comment.id) + return self.notifications_settings.post_comment_reply_notifications and \ + not self.has_disabled_reply_notifications_for_post( + post_id=post_comment.post_id, + post_comment_id=post_comment.id) and \ + not self.has_disabled_reply_notifications_for_post_comment(post_comment_id=post_comment.id) def has_connection_request_notifications_enabled(self): return self.notifications_settings.connection_request_notifications @@ -1181,47 +1201,44 @@ def comment_post_with_id(self, post_id, text): def comment_post(self, post, text): check_can_comment_in_post(user=self, post=post) post_comment = post.comment(text=text, commenter=self) - post_creator = post.creator post_commenter = self - Post = get_post_model() + # create post notifications subscription for user + PostNotificationsSubscription = get_post_notifications_subscription_model() + PostNotificationsSubscription.get_or_create_post_notifications_subscription( + post=post, + subscriber=self, + comment_notifications=True, + comment_reaction_notifications=True, + reaction_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=True + ) + # create post comment notifications subscription for user + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + PostCommentNotificationsSubscription.create_post_comment_notifications_subscription( + post_comment=post_comment, + subscriber=self, + reply_notifications=True, + reaction_notifications=True, + ) + # Language should also be prefetched here, for some reason it doesnt work.... - post_notification_target_users = Post.get_post_comment_notification_target_users(post=post, - post_commenter=post_commenter) + post_notification_target_users = Post.get_post_comment_notification_target_users(post=post) PostCommentNotification = get_post_comment_notification_model() - PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() for post_notification_target_user in post_notification_target_users: if post_notification_target_user.pk == post_commenter.pk or \ not post_notification_target_user.can_see_post_comment(post_comment=post_comment): continue - post_notification_target_user_is_post_creator = post_notification_target_user.id == post_creator.id - post_notification_target_user_is_post_subscriber = \ - post_notification_target_user.are_post_subscription_comment_notifications_enabled_for_post(post=post) - - if post_notification_target_user_is_post_creator: - # send post notification - self._send_post_comment_push_notification(post_comment=post_comment, - target_user=post_notification_target_user) - PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, - owner_id=post_notification_target_user.id) - - elif not post_notification_target_user_is_post_subscriber: - # regular commenter - self._send_post_comment_push_notification(post_comment=post_comment, - target_user=post_notification_target_user) - PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, - owner_id=post_notification_target_user.id) - elif post_notification_target_user_is_post_subscriber: - # send the post subscriber notification - self._send_post_notifications_subscription_comment_push_notification( - post_comment=post_comment, - target_user=post_notification_target_user) - PostSubscriptionCommentNotification.create_post_subscription_comment_notification( - post_comment_id=post_comment.pk, owner_id=post_notification_target_user.id) + + self._send_post_comment_push_notification(post_comment=post_comment, + target_user=post_notification_target_user) + PostCommentNotification.create_post_comment_notification(post_comment_id=post_comment.pk, + owner_id=post_notification_target_user.id) return post_comment @@ -1235,17 +1252,39 @@ def reply_to_comment_with_id_for_post_with_uuid(self, post_comment_id, post_uuid def reply_to_comment_for_post(self, post_comment, post, text): check_can_reply_to_post_comment_for_post(user=self, post_comment=post_comment, post=post) post_comment_reply = post_comment.reply_to_comment(text=text, commenter=self) - comment_creator = post_comment.commenter.id - post_creator = post.creator replier = self - Post = get_post_model() - PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() + # create post notifications subscription for user + PostNotificationsSubscription = get_post_notifications_subscription_model() + PostNotificationsSubscription.get_or_create_post_notifications_subscription( + post=post, + subscriber=self, + comment_notifications=False, + comment_reaction_notifications=True, + reaction_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=True + ) + # create post comment notifications subscription for user + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + PostCommentNotificationsSubscription.get_or_create_post_comment_notifications_subscription( + post_comment=post_comment, + subscriber=self, + reply_notifications=True, + reaction_notifications=False, + ) + # create post comment reply notifications subscription for user + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + PostCommentNotificationsSubscription.create_post_comment_notifications_subscription( + post_comment=post_comment_reply, + subscriber=self, + reaction_notifications=True, + reply_notifications=False, + ) + Post = get_post_model() # Language should also be prefetched here, for some reason it doesnt work.... - post_notification_target_users = Post.get_post_comment_reply_notification_target_users( - post_commenter=self, - parent_post_comment=post_comment) + post_notification_target_users = Post.get_post_comment_notification_target_users(post=post) PostCommentReplyNotification = get_post_comment_reply_notification_model() @@ -1253,31 +1292,11 @@ def reply_to_comment_for_post(self, post_comment, post, text): if post_notification_target_user.pk == replier.pk or \ not post_notification_target_user.can_see_post_comment(post_comment=post_comment_reply): continue - post_notification_target_user_is_post_comment_creator = post_notification_target_user.id == comment_creator - post_notification_target_user_is_post_creator = post_notification_target_user.id == post_creator.id - post_notification_target_user_is_post_subscriber = \ - post_notification_target_user.are_post_subscription_comment_notifications_enabled_for_post(post=post_comment.post) - - if post_notification_target_user_is_post_comment_creator or post_notification_target_user_is_post_creator: - self._send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, - target_user=post_notification_target_user) - PostCommentReplyNotification.create_post_comment_reply_notification( - post_comment_id=post_comment_reply.pk, - owner_id=post_notification_target_user.id) - elif not post_notification_target_user_is_post_subscriber: - self._send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, - target_user=post_notification_target_user) - PostCommentReplyNotification.create_post_comment_reply_notification( - post_comment_id=post_comment_reply.pk, - owner_id=post_notification_target_user.id) - - elif post_notification_target_user_is_post_subscriber: - # send the post subscriber notification - self._send_post_notifications_subscription_comment_push_notification( - post_comment=post_comment_reply, - target_user=post_notification_target_user) - PostSubscriptionCommentNotification.create_post_subscription_comment_notification( - post_comment_id=post_comment_reply.pk, owner_id=post_notification_target_user.id) + self._send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, + target_user=post_notification_target_user) + PostCommentReplyNotification.create_post_comment_reply_notification( + post_comment_id=post_comment_reply.pk, + owner_id=post_notification_target_user.id) return post_comment_reply @@ -2175,12 +2194,51 @@ def close_post(self, post): return post + def create_post_comment_notifications_subscription_for_comment_with_id(self, post_comment_id, + reply_notifications=False, + reaction_notifications=False): + PostComment = get_post_comment_model() + post_comment = PostComment.objects.get(id=post_comment_id) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + + check_can_create_or_update_notifications_subscription_for_post_comment(user=self, post_comment=post_comment) + + post_comment_notifications_subscription = PostCommentNotificationsSubscription. \ + create_post_comment_notifications_subscription( + post_comment=post_comment, + subscriber=self, + reply_notifications=reply_notifications, + reaction_notifications=reaction_notifications + ) + + return post_comment_notifications_subscription + + def update_post_comment_notifications_subscription_for_comment_with_id(self, post_comment_id, + reply_notifications=None, + reaction_notifications=None): + + PostComment = get_post_comment_model() + post_comment = PostComment.objects.get(id=post_comment_id) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + + check_can_create_or_update_notifications_subscription_for_post_comment(user=self, post_comment=post_comment) + + post_comment_notifications_subscription = PostCommentNotificationsSubscription.\ + get_or_create_post_comment_notifications_subscription(post_comment=post_comment, + subscriber=self) + post_comment_notifications_subscription.update( + reply_notifications=reply_notifications, + reaction_notifications=reaction_notifications + ) + + return post_comment_notifications_subscription + def create_post_notifications_subscription_for_post_with_id(self, post_id, - comment_notifications=None, - comment_reaction_notifications=None, - reaction_notifications=None, - reply_notifications=None, - reply_where_commented_notifications=None): + comment_notifications=False, + comment_reaction_notifications=False, + reaction_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=False): Post = get_post_model() post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() @@ -2212,7 +2270,10 @@ def update_post_notifications_subscription_for_post_with_id(self, post_id, check_can_create_or_update_post_notifications_subscription_for_post(user=self, post=post) - post_notifications_subscription = PostNotificationsSubscription.objects.get(post=post, subscriber=self) + post_notifications_subscription = PostNotificationsSubscription.\ + get_or_create_post_notifications_subscription(post=post, + subscriber=self) + post_notifications_subscription.update( comment_notifications=comment_notifications, comment_reaction_notifications=comment_reaction_notifications, @@ -2223,21 +2284,6 @@ def update_post_notifications_subscription_for_post_with_id(self, post_id, return post_notifications_subscription - # def disable_post_subscription_comment_notifications_for_post_with_id(self, post_id): - # Post = get_post_model() - # post = Post.objects.get(id=post_id) - # PostNotificationsSubscription = get_post_notifications_subscription_model() - # - # check_can_disable_post_notifications_subscription_for_post(user=self, post=post) - # - # post_notifications_subscription = PostNotificationsSubscription.\ - # get_or_create_post_notifications_subscription(post=post, subscriber=self) - # - # post_notifications_subscription.comment_notifications = False - # post_notifications_subscription.save() - # - # return post - def are_post_subscription_comment_notifications_enabled_for_post(self, post): PostNotificationsSubscription = get_post_notifications_subscription_model() return PostNotificationsSubscription.are_comment_notifications_enabled_for_user_with_username_and_post_with_id( @@ -3339,26 +3385,18 @@ def _send_password_reset_email_with_token(self, password_reset_token): email.attach_alternative(html_content, 'text/html') email.send() - def _send_post_notifications_subscription_comment_push_notification(self, post_comment, target_user): - target_has_post_notifications_subscription_comment_notifications_enabled = \ - target_user.has_post_subscription_comment_notifications_enabled_for_post_with_id( - post_id=post_comment.post_id) - - if target_has_post_notifications_subscription_comment_notifications_enabled: - helpers.send_post_notifications_subscription_comment_push_notification(post_comment=post_comment, - target_user=target_user) - def _send_post_comment_reply_push_notification(self, post_comment_reply, target_user): post_comment = post_comment_reply.parent_comment target_has_comment_reply_notifications_enabled = \ target_user.has_reply_notifications_enabled_for_post_comment(post_comment=post_comment) + if target_has_comment_reply_notifications_enabled: helpers.send_post_comment_reply_push_notification(post_comment_reply=post_comment_reply, target_user=target_user) def _send_post_comment_push_notification(self, post_comment, target_user): post_notification_target_has_comment_notifications_enabled = \ - target_user.has_comment_notifications_enabled_for_post_with_id(post_id=post_comment.post_id) + target_user.has_comment_notifications_enabled_for_post_with_id(post_id=post_comment.post.id) if post_notification_target_has_comment_notifications_enabled: helpers.send_post_comment_push_notification(post_comment=post_comment, target_user=target_user) diff --git a/openbook_common/serializers_fields/post.py b/openbook_common/serializers_fields/post.py index 0ffe5260..a8043745 100644 --- a/openbook_common/serializers_fields/post.py +++ b/openbook_common/serializers_fields/post.py @@ -158,18 +158,3 @@ def to_representation(self, post): return is_encircled - -class PostSubscriptionCommentNotificationsEnabledField(Field): - def __init__(self, **kwargs): - kwargs['source'] = '*' - kwargs['read_only'] = True - super(PostSubscriptionCommentNotificationsEnabledField, self).__init__(**kwargs) - - def to_representation(self, post): - request = self.context.get('request') - request_user = request.user - - if request_user.is_anonymous: - return False - - return request_user.are_post_subscription_comment_notifications_enabled_for_post(post=post) diff --git a/openbook_common/utils/model_loaders.py b/openbook_common/utils/model_loaders.py index 69e8b305..4c64af20 100644 --- a/openbook_common/utils/model_loaders.py +++ b/openbook_common/utils/model_loaders.py @@ -41,6 +41,10 @@ def get_post_notifications_subscription_model(): return apps.get_model('openbook_posts.PostNotificationsSubscription') +def get_post_comment_notifications_subscription_model(): + return apps.get_model('openbook_posts.PostCommentNotificationsSubscription') + + def get_proxy_blacklist_domain_model(): return apps.get_model('openbook_common.ProxyBlacklistedDomain') @@ -133,10 +137,6 @@ def get_post_comment_notification_model(): return apps.get_model('openbook_notifications.PostCommentNotification') -def get_post_subscription_comment_notification_model(): - return apps.get_model('openbook_notifications.PostSubscriptionCommentNotification') - - def get_post_user_mention_notification_model(): return apps.get_model('openbook_notifications.PostUserMentionNotification') diff --git a/openbook_notifications/helpers.py b/openbook_notifications/helpers.py index 4663b5a3..8e79a89c 100644 --- a/openbook_notifications/helpers.py +++ b/openbook_notifications/helpers.py @@ -55,6 +55,7 @@ def send_post_comment_reply_push_notification(post_comment_reply, target_user): target_user_is_post_comment_creator = target_user.id == comment_creator_id target_user_is_post_creator = target_user.id == post_creator.id + target_user_has_replied = target_user.has_replied_on_post_with_id(post_id=post_comment_reply.post.pk) target_user_language_code = get_notification_language_code_for_target_user( target_user) @@ -73,13 +74,20 @@ def send_post_comment_reply_push_notification(post_comment_reply, target_user): 'post_commenter_username': replier.username, 'post_commenter_name': replier.profile.name, }} - else: + elif target_user_has_replied: notification_message = { "en": _( '%(post_commenter_name)s · @%(post_commenter_username)s replied on a comment you also replied on.') % { 'post_commenter_username': replier.username, 'post_commenter_name': replier.profile.name, }} + else: + notification_message = { + "en": _( + '%(post_commenter_name)s · @%(post_commenter_username)s replied on a post you are following.') % { + 'post_commenter_username': replier.username, + 'post_commenter_name': replier.profile.name, + }} notification_group = NOTIFICATION_GROUP_LOW_PRIORITY @@ -103,9 +111,11 @@ def send_post_comment_push_notification(post_comment, target_user): post_creator = post_comment.post.creator post_commenter = post_comment.commenter - post_notification_target_user_is_post_creator = target_user.id == post_creator.id target_user_language_code = get_notification_language_code_for_target_user( target_user) + post_notification_target_user_is_post_creator = target_user.id == post_creator.id + post_notification_target_user_has_commented = \ + target_user.has_commented_post_with_id(post_id=post_comment.post.pk) with translation.override(target_user_language_code): if post_notification_target_user_is_post_creator: @@ -114,49 +124,17 @@ def send_post_comment_push_notification(post_comment, target_user): 'post_commenter_username': post_commenter.username, 'post_commenter_name': post_commenter.profile.name, }} - else: + elif post_notification_target_user_has_commented: notification_message = { "en": _( '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you also commented on.') % { 'post_commenter_username': post_commenter.username, 'post_commenter_name': post_commenter.profile.name, }} - - notification_group = NOTIFICATION_GROUP_LOW_PRIORITY - - one_signal_notification = onesignal_sdk.Notification(post_body={ - "contents": notification_message - }) - - notification_data = { - 'type': Notification.POST_COMMENT, - } - - one_signal_notification.set_parameter('data', notification_data) - one_signal_notification.set_parameter('!thread_id', notification_group) - one_signal_notification.set_parameter('android_group', notification_group) - - _send_notification_to_user(notification=one_signal_notification, user=target_user) - - -def send_post_notifications_subscription_comment_push_notification(post_comment, target_user): - Notification = get_notification_model() - post_commenter = post_comment.commenter - target_user_language_code = get_notification_language_code_for_target_user( - target_user) - - with translation.override(target_user_language_code): - if post_comment.parent_comment is None: - notification_message = { - "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you are subscribed to.') % { - 'post_commenter_username': post_commenter.username, - 'post_commenter_name': post_commenter.profile.name, - }} else: notification_message = { "en": _( - '%(post_commenter_name)s · @%(post_commenter_username)s replied on a post you are subscribed to.') % { + '%(post_commenter_name)s · @%(post_commenter_username)s commented on a post you are following.') % { 'post_commenter_username': post_commenter.username, 'post_commenter_name': post_commenter.profile.name, }} @@ -168,7 +146,7 @@ def send_post_notifications_subscription_comment_push_notification(post_comment, }) notification_data = { - 'type': Notification.POST_SUBSCRIPTION_COMMENT, + 'type': Notification.POST_COMMENT, } one_signal_notification.set_parameter('data', notification_data) diff --git a/openbook_notifications/models/__init__.py b/openbook_notifications/models/__init__.py index 83162b7a..39155c47 100644 --- a/openbook_notifications/models/__init__.py +++ b/openbook_notifications/models/__init__.py @@ -11,4 +11,3 @@ from .post_user_mention_notification import PostUserMentionNotification from .community_new_post_notification import CommunityNewPostNotification from .user_new_post_notification import UserNewPostNotification -from .post_subscription_comment_notification import PostSubscriptionCommentNotification diff --git a/openbook_posts/models.py b/openbook_posts/models.py index c3e3aeae..cda8e704 100644 --- a/openbook_posts/models.py +++ b/openbook_posts/models.py @@ -41,7 +41,7 @@ get_post_user_mention_model, get_post_comment_user_mention_model, get_community_notifications_subscription_model, \ get_community_new_post_notification_model, get_user_new_post_notification_model, \ get_hashtag_model, get_user_notifications_subscription_model, get_trending_post_model, \ - get_post_comment_reaction_notification_model + get_post_comment_reaction_notification_model, get_post_notifications_subscription_model from imagekit.models import ProcessedImageField from openbook_moderation.models import ModeratedObject @@ -243,57 +243,21 @@ def _get_trending_posts_old_query(cls): return trending_posts_query @classmethod - def get_post_comment_notification_target_users(cls, post, post_commenter): + def get_post_comment_notification_target_users(cls, post): """ - Returns the users that should be notified of a post comment. + Returns the users that qualify to be notified of a post comment. This includes the post creator and other post commenters and post subscribers :return: """ - # Add other post commenters and subscribers, exclude replies to comments, the post commenter - # post subscribers - post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk, - post_notifications_subscriptions__comment_notifications=True) - - other_commenters_query = Q(posts_comments__post_id=post.pk, posts_comments__parent_comment_id=None,) + post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk) - other_target_users = User.objects.\ + target_users = User.objects.\ only('id', 'username', 'notifications_settings__post_comment_notifications').\ - filter((post_subscribers_query | other_commenters_query) & ~Q(id=post_commenter.pk)) + filter(post_subscribers_query) - post_creator = User.objects. \ - only('id', 'username', 'notifications_settings__post_comment_notifications'). \ - filter(pk=post.creator_id) - - return other_target_users.union(post_creator) - - @classmethod - def get_post_comment_reply_notification_target_users(cls, post_commenter, parent_post_comment): - """ - Returns the users that should be notified of a post comment reply. - :return: - """ - post = parent_post_comment.post - # post subscribers - post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk, - post_notifications_subscriptions__comment_notifications=True) - - other_repliers_query = Q(posts_comments__parent_comment_id=parent_post_comment.pk, ) - # Add other post commenters, exclude non replies, the post commenter - other_repliers = User.objects.\ - only('id', 'username', 'notifications_settings__post_comment_reply_notifications').\ - filter((post_subscribers_query | other_repliers_query) & ~Q(id=post_commenter.pk)) - - # Add post comment creator - post_comment_creator = User.objects. \ - only('id', 'username', 'notifications_settings__post_comment_reply_notifications'). \ - filter(pk=parent_post_comment.commenter_id) - # Add post creator - post_creator = User.objects. \ - only('id', 'username', 'notifications_settings__post_comment_reply_notifications'). \ - filter(pk=post.creator.id) - return other_repliers.union(post_comment_creator, post_creator) + return target_users @classmethod def get_community_notification_target_subscriptions(cls, post): @@ -544,6 +508,7 @@ def _publish(self): self.status = Post.STATUS_PUBLISHED self.created = timezone.now() self._process_post_subscribers() + self._subscribe_creator_to_post_notifications() self.save() def is_draft(self): @@ -805,6 +770,19 @@ def _process_post_subscribers(self): user_notifications_subscription_id=subscription.pk) send_user_new_post_push_notification(user_notifications_subscription=subscription, post=self) + def _subscribe_creator_to_post_notifications(self): + PostNotificationsSubscription = get_post_notifications_subscription_model() + + PostNotificationsSubscription.create_post_notifications_subscription( + post=self, + subscriber=self.creator, + comment_notifications=True, + comment_reaction_notifications=True, + reaction_notifications=True, + reply_notifications=True, + reply_where_commented_notifications=True + ) + class TopPost(models.Model): post = models.OneToOneField(Post, on_delete=models.CASCADE, related_name='top_post') @@ -1333,9 +1311,14 @@ class Meta: unique_together = ('post_comment', 'subscriber',) @classmethod - def create_post_comment_notifications_subscription(cls, subscriber, post_comment): + def create_post_comment_notifications_subscription(cls, subscriber, post_comment, + reply_notifications=False, + reaction_notifications=False): if not cls.objects.filter(subscriber=subscriber, post_comment=post_comment).exists(): - return cls.objects.create(subscriber=subscriber, post_comment=post_comment) + return cls.objects.create(subscriber=subscriber, + post_comment=post_comment, + reply_notifications=reply_notifications, + reaction_notifications=reaction_notifications) post_comment_notifications_subscription = cls.objects.get(subscriber=subscriber, post_comment=post_comment) post_comment_notifications_subscription.save() @@ -1343,14 +1326,18 @@ def create_post_comment_notifications_subscription(cls, subscriber, post_comment return post_comment_notifications_subscription @classmethod - def get_or_create_post_comment_notifications_subscription(cls, subscriber, post_comment): + def get_or_create_post_comment_notifications_subscription(cls, subscriber, post_comment, + reply_notifications=None, + reaction_notifications=None): try: post_comment_notifications_subscription = cls.objects.get(subscriber_id=subscriber.pk, post_comment_id=post_comment.pk) except cls.DoesNotExist: post_comment_notifications_subscription = cls.create_post_comment_notifications_subscription( subscriber=subscriber, - post_comment=post_comment + post_comment=post_comment, + reply_notifications=reply_notifications, + reaction_notifications=reaction_notifications ) return post_comment_notifications_subscription @@ -1367,13 +1354,25 @@ def post_comment_notifications_subscription_exists(cls, subscriber, post_comment def is_user_with_username_subscribed_to_reply_notifications_for_post_comment_with_id(cls, username, post_comment_id): return cls.objects.filter(post_comment_id=post_comment_id, subscriber__username=username, - comment_notifications=True).exists() + reply_notifications=True).exists() @classmethod def are_reply_notifications_enabled_for_user_with_username_and_post_comment_with_id(cls, username, post_comment_id): return cls.objects.filter(post_comment_id=post_comment_id, subscriber__username=username, - comment_notifications=True).exists() + reply_notifications=True).exists() + + def update(self, + reaction_notifications=None, + reply_notifications=None): + + if reply_notifications is not None: + self.reply_notifications = reply_notifications + + if reaction_notifications is not None: + self.reaction_notifications = reaction_notifications + + self.save() class PostNotificationsSubscription(models.Model): @@ -1393,11 +1392,12 @@ class Meta: @classmethod def create_post_notifications_subscription(cls, subscriber, post, - comment_notifications=None, - comment_reaction_notifications=None, - reaction_notifications=None, - reply_notifications=None, - reply_where_commented_notifications=None): + comment_notifications=False, + comment_reaction_notifications=False, + reaction_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=False): + if not cls.objects.filter(subscriber=subscriber, post=post).exists(): return cls.objects.create(subscriber=subscriber, post=post, @@ -1411,11 +1411,12 @@ def create_post_notifications_subscription(cls, subscriber, post, return post_notifications_subscription @classmethod - def get_or_create_post_notifications_subscription(cls, subscriber, post, comment_notifications=None, - comment_reaction_notifications=None, - reaction_notifications=None, - reply_notifications=None, - reply_where_commented_notifications=None): + def get_or_create_post_notifications_subscription(cls, subscriber, post, + comment_notifications=False, + comment_reaction_notifications=False, + reaction_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=False): try: post_notifications_subscription = cls.objects.get(subscriber_id=subscriber.pk, post_id=post.pk) From a3b39005bec2bfb9cb1462cda9a310804f66bb83 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 24 Mar 2020 15:37:09 +0100 Subject: [PATCH 21/40] :recycle: refactor all tests, n fix errors --- .../tests/views/moderated_object.py | 10 +- openbook_posts/tests/views/test_post.py | 22 +- .../tests/views/test_post_comment_replies.py | 244 +++++++++++++----- .../tests/views/test_post_comments.py | 110 ++++---- 4 files changed, 255 insertions(+), 131 deletions(-) diff --git a/openbook_moderation/tests/views/moderated_object.py b/openbook_moderation/tests/views/moderated_object.py index 94e5266e..3dd3208c 100644 --- a/openbook_moderation/tests/views/moderated_object.py +++ b/openbook_moderation/tests/views/moderated_object.py @@ -20,7 +20,7 @@ get_post_comment_user_mention_notification_model, get_post_comment_user_mention_model, \ get_post_reaction_notification_model, get_post_user_mention_model, get_post_user_mention_notification_model, \ get_community_invite_notification_model, get_follow_notification_model, get_connection_request_notification_model, \ - get_connection_confirmed_notification_model, get_post_subscription_comment_notification_model + get_connection_confirmed_notification_model from openbook_communities.models import Community from openbook_moderation.models import ModeratedObject, ModeratedObjectDescriptionChangedLog, \ ModeratedObjectCategoryChangedLog, ModerationPenalty, ModerationCategory, ModeratedObjectStatusChangedLog, \ @@ -1468,10 +1468,10 @@ def test_approving_community_post_moderated_object_deletes_post_subscription_com headers = make_authentication_headers_for_user(global_moderator) response = self.client.post(url, **headers) - PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() + PostCommentNotification = get_post_comment_notification_model() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(PostSubscriptionCommentNotification.objects.filter( + self.assertFalse(PostCommentNotification.objects.filter( post_comment=community_post_comment).exists()) def test_approving_community_post_moderated_object_deletes_comment_reply_notifications(self): @@ -1564,10 +1564,10 @@ def test_approving_community_post_moderated_object_deletes_post_subscription_com headers = make_authentication_headers_for_user(global_moderator) response = self.client.post(url, **headers) - PostSubscriptionCommentNotification = get_post_subscription_comment_notification_model() + PostCommentReplyNotification = get_post_comment_reply_notification_model() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(PostSubscriptionCommentNotification.objects.filter( + self.assertFalse(PostCommentReplyNotification.objects.filter( post_comment=community_post_comment_reply).exists()) def test_approving_community_post_moderated_object_deletes_comment_reaction_notifications(self): diff --git a/openbook_posts/tests/views/test_post.py b/openbook_posts/tests/views/test_post.py index cea9e24a..98f7f1f2 100644 --- a/openbook_posts/tests/views/test_post.py +++ b/openbook_posts/tests/views/test_post.py @@ -1304,9 +1304,9 @@ def _get_url(self, post): }) -class SubscribePostSubscriptionCommentNotificationsAPITests(OpenbookAPITestCase): +class PostNotificationsSubscriptionSettingsAPITests(OpenbookAPITestCase): """ - SubscribePostSubscriptionCommentNotifications API + PostNotificationsSubscriptionSettings API """ fixtures = [ @@ -1540,7 +1540,7 @@ def test_can_unsubscribe_comment_notifications_for_public_post(self): headers = make_authentication_headers_for_user(subscriber) post = user.create_public_post(text=make_fake_post_text()) - subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id) url = self._get_url(post) @@ -1583,7 +1583,7 @@ def test_can_unsubscribe_to_comment_notifications_for_post_if_part_of_encircled_ post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) - user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) url = self._get_url(post) @@ -1604,7 +1604,7 @@ def test_can_unsubscribe_to_comment_notifications_for_community_post_if_public(s headers = make_authentication_headers_for_user(user) user.join_community_with_name(community_name=community.name) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) - user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) url = self._get_url(post) @@ -1625,7 +1625,7 @@ def test_cannot_unsubscribe_to_comment_notifications_for_closed_community_post_i headers = make_authentication_headers_for_user(user) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) - user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) post.is_closed = True post.save() @@ -1648,7 +1648,7 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_c headers = make_authentication_headers_for_user(user) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) - user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) post.is_closed = True post.save() @@ -1671,7 +1671,7 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_admi headers = make_authentication_headers_for_user(admin) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) - admin.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + admin.create_post_notifications_subscription_for_post_with_id(post_id=post.id) post.is_closed = True post.save() @@ -1698,7 +1698,7 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_m headers = make_authentication_headers_for_user(moderator) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) - moderator.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + moderator.create_post_notifications_subscription_for_post_with_id(post_id=post.id) post.is_closed = True post.save() @@ -1745,7 +1745,7 @@ def test_can_unsubscribe_to_comment_notifications_for_community_post_if_private_ community_name=community.name) user.join_community_with_name(community_name=community.name) - user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) url = self._get_url(post) @@ -1755,7 +1755,7 @@ def test_can_unsubscribe_to_comment_notifications_for_community_post_if_private_ self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) def _get_url(self, post): - return reverse('subscribe-post-subscription-comment-notifications', kwargs={ + return reverse('post-notifications-subscription-settings', kwargs={ 'post_uuid': post.uuid }) diff --git a/openbook_posts/tests/views/test_post_comment_replies.py b/openbook_posts/tests/views/test_post_comment_replies.py index abcdc60d..9c12b5d6 100644 --- a/openbook_posts/tests/views/test_post_comment_replies.py +++ b/openbook_posts/tests/views/test_post_comment_replies.py @@ -2,6 +2,7 @@ import json import random from unittest import mock +from unittest.mock import call from django.urls import reverse from faker import Faker @@ -12,7 +13,7 @@ from openbook_common.tests.helpers import make_authentication_headers_for_user, make_fake_post_text, \ make_fake_post_comment_text, make_user, make_circle, make_community, make_private_community -from openbook_notifications.models import PostCommentReplyNotification, PostSubscriptionCommentNotification +from openbook_notifications.models import PostCommentReplyNotification from openbook_posts.models import PostComment, PostCommentUserMention logger = logging.getLogger(__name__) @@ -1384,7 +1385,12 @@ def test_commenting_reply_in_commented_post_by_foreign_user_creates_foreign_noti foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - foreign_user.mute_post_with_id(post_id=post.pk) + # mute post notifications + foreign_user.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + comment_notifications=False, + reply_where_commented_notifications=False, + reply_notifications=False) reply_comment_text = make_fake_post_comment_text() @@ -1504,17 +1510,22 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(s user = make_user() headers = make_authentication_headers_for_user(user) - post_creator = make_user() + post_creator = post_commenter = make_user() foreign_user = make_user() post = post_creator.create_public_post(text=make_fake_post_text()) - post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + post_comment = post_commenter.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - foreign_user.mute_post(post=post) + # mute post notifications + foreign_user.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + comment_notifications=False, + reply_where_commented_notifications=False, + reply_notifications=False) reply_comment_text = make_fake_post_comment_text() @@ -1529,10 +1540,14 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(s commenter_id=user.pk, parent_comment_id=post_comment.id) - send_post_comment_reply_push_notification_call.assert_called_with( - post_comment_reply=post_comment_reply, - target_user=post_creator - ) + calls = [ + call( + post_comment_reply=post_comment_reply, + target_user=post_commenter + ) + ] + + send_post_comment_reply_push_notification_call.assert_has_calls(calls, any_order=True) @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creator(self, @@ -1547,12 +1562,7 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creato foreign_user = make_user() post = post_creator.create_public_post(text=make_fake_post_text()) - post_comment = user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, - post_uuid=post.uuid, - text=make_fake_post_comment_text()) - - foreign_user.mute_post(post=post) + post_comment = foreign_user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) reply_comment_text = make_fake_post_comment_text() @@ -1567,10 +1577,18 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creato commenter_id=user.pk, parent_comment_id=post_comment.id) - send_post_comment_reply_push_notification_call.assert_called_with( - post_comment_reply=post_comment_reply, - target_user=post_creator - ) + calls = [ + call( + post_comment_reply=post_comment_reply, + target_user=post_creator + ), + call( + post_comment_reply=post_comment_reply, + target_user=foreign_user + ) + ] + + send_post_comment_reply_push_notification_call.assert_has_calls(calls, any_order=True) @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_other_replier(self, @@ -1591,7 +1609,12 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_other_repli post_uuid=post.uuid, text=make_fake_post_comment_text()) - post_creator.mute_post(post=post) + # mute post notifications + post_creator.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + comment_notifications=False, + reply_where_commented_notifications=False, + reply_notifications=False) reply_comment_text = make_fake_post_comment_text() @@ -1615,7 +1638,7 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_other_repli def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_muted(self, send_post_comment_reply_push_notification_call): """ - should NOT send a push notification when replying on a foreign post comment when muted + should NOT send a push notification when replying on a foreign post comment when comment is muted """ user = make_user() headers = make_authentication_headers_for_user(user) @@ -1627,12 +1650,18 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_mut post = post_creator.create_public_post(text=make_fake_post_text()) post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - post_creator.mute_post_comment_with_id(post_comment_id=post_comment.pk) + # mute post comment for creator + post_creator.update_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.pk, + reply_notifications=False) foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - foreign_user.mute_post_comment_with_id(post_comment_id=post_comment.pk) + # mute post comment for foreign user + foreign_user.update_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.pk, + reply_notifications=False) send_post_comment_reply_push_notification_call.reset_mock() @@ -1661,12 +1690,24 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_pos post = post_creator.create_public_post(text=make_fake_post_text()) post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - post_creator.mute_post_with_id(post_id=post.pk) + # mute post creator notifications + post_creator.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + comment_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=False + ) foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - foreign_user.mute_post_with_id(post_id=post.pk) + + foreign_user.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + comment_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=False + ) send_post_comment_reply_push_notification_call.reset_mock() @@ -2080,9 +2121,9 @@ def test_should_retrieve_comment_replies_slice_with_sort_for_min_id_and_max_id(s # post notifications subscription tests - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_foreign_user_replying_on_post_comment_sends_push_notification_to_subscriber(self, - send_post_notifications_subscription_comment_push_notification_call): + send_post_comment_reply_push_notification_call): """ should send a push notification to the subscriber when someone replies on a post comment """ @@ -2098,9 +2139,10 @@ def test_foreign_user_replying_on_post_comment_sends_push_notification_to_subscr data = self._get_create_post_comment_request_data(reply_comment_text) # subscribe to post comment notifications - post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post_subscriber.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, reply_notifications=True) - send_post_notifications_subscription_comment_push_notification_call.reset_mock() + send_post_comment_reply_push_notification_call.reset_mock() url = self._get_url(post, post_comment) self.client.put(url, data, **headers) @@ -2109,14 +2151,22 @@ def test_foreign_user_replying_on_post_comment_sends_push_notification_to_subscr commenter_id=foreign_user.pk, parent_comment_id=post_comment.id) - send_post_notifications_subscription_comment_push_notification_call.assert_called_with( - post_comment=post_comment_reply, - target_user=post_subscriber - ) + calls = [ + call( + post_comment_reply=post_comment_reply, + target_user=post_subscriber + ), + call( + post_comment_reply=post_comment_reply, + target_user=post_creator + ) + ] - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + send_post_comment_reply_push_notification_call.assert_has_calls(calls, any_order=True) + + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_foreign_user_replying_on_post_comment_doesnt_send_push_notification_to_subscriber_if_muted(self, - send_post_notifications_subscription_comment_push_notification_call): + send_post_comment_reply_push_notification_call): """ should NOT send a push notification to the subscriber when someone replies on a post comment if post is muted """ @@ -2132,19 +2182,63 @@ def test_foreign_user_replying_on_post_comment_doesnt_send_push_notification_to_ data = self._get_create_post_comment_request_data(reply_comment_text) # subscribe to post comment notifications and mute - post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) - post_subscriber.mute_post(post=post) + post_subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + post_subscriber.update_post_notifications_subscription_for_post_with_id( + post_id=post.id, + reply_notifications=False, + reply_where_commented_notifications=False) + + send_post_comment_reply_push_notification_call.reset_mock() + + url = self._get_url(post, post_comment) + self.client.put(url, data, **headers) + + # once for creator + send_post_comment_reply_push_notification_call.assert_called_once() + + + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') + def test_replying_on_foreign_post_comment_doesnt_send_push_notification_to_subscriber_when_comment_muted(self, + send_post_comment_reply_push_notification_call): + """ + should NOT send a push notification to subscriber when replying on a foreign post comment when comment is muted + """ + user = make_user() + headers = make_authentication_headers_for_user(user) + post_subscriber = make_user() + + post_creator = make_user() + + post = post_creator.create_public_post(text=make_fake_post_text()) + post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + + # mute post comment for creator + post_creator.update_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.pk, + reply_notifications=False) + + # subscribe to post comment notifications and mute + post_subscriber.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + reply_notifications=True) + post_subscriber.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.pk, + reply_notifications=False) - send_post_notifications_subscription_comment_push_notification_call.reset_mock() + send_post_comment_reply_push_notification_call.reset_mock() + + reply_comment_text = make_fake_post_comment_text() + + data = self._get_create_post_comment_request_data(reply_comment_text) url = self._get_url(post, post_comment) self.client.put(url, data, **headers) - send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + send_post_comment_reply_push_notification_call.assert_not_called() - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_does_not_send_subscription_notification_to_a_subscribed_commenter(self, - send_post_notifications_subscription_comment_push_notification_call): + send_post_comment_reply_push_notification_call): """ should NOT send post subscription push notification to the commenter when replying on a foreign post comment """ @@ -2158,23 +2252,34 @@ def test_replying_on_foreign_post_comment_does_not_send_subscription_notificatio post = post_creator.create_public_post(text=make_fake_post_text()) post_comment = commenter.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, - post_uuid=post.uuid, - text=make_fake_post_comment_text()) + foreign_post_comment = foreign_user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) # subscribe to post comment notifications - commenter.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + commenter.create_post_notifications_subscription_for_post_with_id(post_id=post.id) reply_comment_text = make_fake_post_comment_text() data = self._get_create_post_comment_request_data(reply_comment_text) - send_post_notifications_subscription_comment_push_notification_call.reset_mock() + send_post_comment_reply_push_notification_call.reset_mock() - url = self._get_url(post, post_comment) + url = self._get_url(post, foreign_post_comment) self.client.put(url, data, **headers) - send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + post_comment_reply = PostComment.objects.get(commenter=user, parent_comment_id=foreign_post_comment.id) + + calls = [ + call( + post_comment_reply=post_comment_reply, + target_user=foreign_user + ), + call( + post_comment_reply=post_comment_reply, + target_user=post_creator + ) + ] + # only called for foreign user and creator + send_post_comment_reply_push_notification_call.assert_has_calls(calls, any_order=True) def test_replying_on_post_comment_doesnt_create_post_subscription_push_notification_when_user_blocked(self): """ @@ -2194,7 +2299,7 @@ def test_replying_on_post_comment_doesnt_create_post_subscription_push_notificat # Block user blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) # subscribe to post comment notifications - blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + blocking_user_aka_subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id) reply_comment_text = make_fake_post_comment_text() @@ -2203,12 +2308,12 @@ def test_replying_on_post_comment_doesnt_create_post_subscription_push_notificat url = self._get_url(post, post_comment) self.client.put(url, data, **headers) - self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=reply_comment_text, + self.assertFalse(PostCommentReplyNotification.objects.filter(post_comment__text=reply_comment_text, notification__owner=blocking_user_aka_subscriber).exists()) - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_post_comment_doesnt_send_post_subscription_push_notification_when_user_blocked(self, - send_post_notifications_subscription_comment_push_notification_call): + send_post_comment_reply_push_notification_call): """ should NOT send push notification to blocking user when the blocked user replies on a post the blocking user is subscribed to @@ -2217,17 +2322,25 @@ def test_replying_on_post_comment_doesnt_send_post_subscription_push_notificatio headers = make_authentication_headers_for_user(blocked_user) post_creator = make_user() + post_commenter = post_creator blocking_user_aka_subscriber = make_user() post = post_creator.create_public_post(text=make_fake_post_text()) - post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) + post_comment = post_commenter.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) # Block user blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) # subscribe to post comment notifications - blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + blocking_user_aka_subscriber.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=False, + comment_reaction_notifications=False, + reaction_notifications=False, + reply_notifications=True, + reply_where_commented_notifications=True + ) - send_post_notifications_subscription_comment_push_notification_call.reset_mock() + send_post_comment_reply_push_notification_call.reset_mock() reply_comment_text = make_fake_post_comment_text() @@ -2236,8 +2349,13 @@ def test_replying_on_post_comment_doesnt_send_post_subscription_push_notificatio url = self._get_url(post, post_comment) self.client.put(url, data, **headers) - # assert notification only for the post creator, not blocking user who blocked the commenting user - send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + post_comment_reply = PostComment.objects.get(commenter=blocked_user, parent_comment_id=post_comment.id) + + # assert notification only for the post commenter, not blocking user who blocked the commenting user + send_post_comment_reply_push_notification_call.assert_called_once_with( + post_comment_reply=post_comment_reply, + target_user=post_commenter + ) def test_reply_in_community_post_does_not_create_foreign_user_post_subscription_notification_when_closed(self): """ @@ -2254,7 +2372,7 @@ def test_reply_in_community_post_does_not_create_foreign_user_post_subscription_ post_comment = post_creator.comment_post(post=post, text=make_fake_post_text()) # subscribe to notifications - foreign_user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + foreign_user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) # post will be closed now post.is_closed = True post.save() @@ -2266,8 +2384,8 @@ def test_reply_in_community_post_does_not_create_foreign_user_post_subscription_ url = self._get_url(post, post_comment) self.client.put(url, data, **headers) - self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=reply_comment_text, - notification__owner=foreign_user).exists()) + self.assertFalse(PostCommentReplyNotification.objects.filter(post_comment__text=reply_comment_text, + notification__owner=foreign_user).exists()) def test_reply_in_community_post_by_admin_does_create_subscription_notification_to_another_admin_when_closed(self): """ @@ -2285,7 +2403,7 @@ def test_reply_in_community_post_by_admin_does_create_subscription_notification_ post_comment = post_creator.comment_post(post=post, text=make_fake_post_text()) # subscribe to notifications - admin.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + admin.create_post_notifications_subscription_for_post_with_id(post_id=post.id) # post will be closed now post.is_closed = True post.save() @@ -2297,8 +2415,8 @@ def test_reply_in_community_post_by_admin_does_create_subscription_notification_ url = self._get_url(post, post_comment) self.client.put(url, data, **headers) - self.assertTrue(PostSubscriptionCommentNotification.objects.filter(post_comment__text=reply_comment_text, - notification__owner=admin).exists()) + self.assertTrue(PostCommentReplyNotification.objects.filter(post_comment__text=reply_comment_text, + notification__owner=admin).exists()) def _get_create_post_comment_request_data(self, reply_comment_text): return { diff --git a/openbook_posts/tests/views/test_post_comments.py b/openbook_posts/tests/views/test_post_comments.py index fb2cdde4..0ace9091 100644 --- a/openbook_posts/tests/views/test_post_comments.py +++ b/openbook_posts/tests/views/test_post_comments.py @@ -4,7 +4,7 @@ from faker import Faker from rest_framework import status from unittest import mock -from unittest.mock import ANY +from unittest.mock import call from openbook_common.tests.models import OpenbookAPITestCase import logging @@ -16,7 +16,7 @@ from openbook_hashtags.models import Hashtag from openbook_moderation.models import ModeratedObject from openbook_notifications.models import PostCommentNotification, PostCommentReplyNotification, \ - PostCommentUserMentionNotification, Notification, PostSubscriptionCommentNotification + PostCommentUserMentionNotification, Notification from openbook_posts.models import PostComment, PostCommentUserMention, Post logger = logging.getLogger(__name__) @@ -1540,7 +1540,10 @@ def test_commenting_in_commented_post_by_foreign_user_creates_foreign_notificati foreign_user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - foreign_user.mute_post_with_id(post_id=post.pk) + foreign_user.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + comment_notifications=False + ) post_comment_text = make_fake_post_comment_text() @@ -2058,9 +2061,9 @@ def test_can_retrieve_moderated_pending_community_post_comments(self): # post notifications subscription tests - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_foreign_user_commenting_on_post_comment_sends_push_notification_to_subscriber(self, - send_post_notifications_subscription_comment_push_notification_call): + send_post_comment_push_notification_call): """ should send a push notification to the subscriber when someone comments on a post """ @@ -2074,9 +2077,9 @@ def test_foreign_user_commenting_on_post_comment_sends_push_notification_to_subs data = self._get_create_post_comment_request_data(comment_text) # subscribe to post comment notifications - post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post_subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) - send_post_notifications_subscription_comment_push_notification_call.reset_mock() + send_post_comment_push_notification_call.reset_mock() url = self._get_url(post) self.client.put(url, data, **headers) @@ -2086,14 +2089,22 @@ def test_foreign_user_commenting_on_post_comment_sends_push_notification_to_subs post_id=post.id ) - send_post_notifications_subscription_comment_push_notification_call.assert_called_with( - post_comment=post_comment, - target_user=post_subscriber - ) + calls = [ + call( + post_comment=post_comment, + target_user=post_creator + ), + call( + post_comment=post_comment, + target_user=post_subscriber + ) + ] + + send_post_comment_push_notification_call.assert_has_calls(calls, any_order=True) - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_foreign_user_commenting_on_post_comment_doesnt_send_push_notification_to_subscriber_if_muted(self, - send_post_notifications_subscription_comment_push_notification_call): + send_post_comment_push_notification_call): """ should NOT send a push notification to the subscriber when someone comments on a post if post is muted """ @@ -2106,41 +2117,27 @@ def test_foreign_user_commenting_on_post_comment_doesnt_send_push_notification_t comment_text = make_fake_post_comment_text() data = self._get_create_post_comment_request_data(comment_text) - # subscribe to post comment notifications and mute - post_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) - post_subscriber.mute_post(post=post) - - send_post_notifications_subscription_comment_push_notification_call.reset_mock() - - url = self._get_url(post) - self.client.put(url, data, **headers) - - send_post_notifications_subscription_comment_push_notification_call.assert_not_called() - - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') - def test_commenting_on_post_does_not_send_subscription_notification_to_the_subscribed_post_creator(self, - send_post_notifications_subscription_comment_push_notification_call): - """ - should NOT send post subscription push notification to the post creator when commenting on a post - """ - user = make_user() - headers = make_authentication_headers_for_user(user) - - post_creator = make_user() - post = post_creator.create_public_post(text=make_fake_post_text()) - # subscribe to post comment notifications - post_creator.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + post_subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) - comment_text = make_fake_post_comment_text() - data = self._get_create_post_comment_request_data(comment_text) + # mute comment notifications + post_subscriber.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + comment_notifications=False + ) - send_post_notifications_subscription_comment_push_notification_call.reset_mock() + send_post_comment_push_notification_call.reset_mock() url = self._get_url(post) self.client.put(url, data, **headers) - send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + post_comment = PostComment.objects.get(commenter=foreign_user, text=comment_text) + + # called only for creator + send_post_comment_push_notification_call.assert_called_with( + post_comment=post_comment, + target_user=post_creator + ) def test_commenting_on_post_doesnt_create_post_subscription_push_notification_when_user_blocked(self): """ @@ -2159,7 +2156,7 @@ def test_commenting_on_post_doesnt_create_post_subscription_push_notification_wh # Block user blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) # subscribe to post comment notifications - blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + blocking_user_aka_subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) comment_text = make_fake_post_comment_text() @@ -2168,12 +2165,12 @@ def test_commenting_on_post_doesnt_create_post_subscription_push_notification_wh url = self._get_url(post) self.client.put(url, data, **headers) - self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=comment_text, + self.assertFalse(PostCommentNotification.objects.filter(post_comment__text=comment_text, notification__owner=blocking_user_aka_subscriber).exists()) - @mock.patch('openbook_notifications.helpers.send_post_notifications_subscription_comment_push_notification') + @mock.patch('openbook_notifications.helpers.send_post_comment_push_notification') def test_commenting_on_post_doesnt_send_post_subscription_push_notification_when_user_blocked(self, - send_post_notifications_subscription_comment_push_notification_call): + send_post_comment_push_notification_call): """ should NOT send push notification to blocking user when the blocked user comments on a post the blocking user is subscribed to @@ -2189,9 +2186,9 @@ def test_commenting_on_post_doesnt_send_post_subscription_push_notification_when # Block user blocking_user_aka_subscriber.block_user_with_id(user_id=blocked_user.pk) # subscribe to post comment notifications - blocking_user_aka_subscriber.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + blocking_user_aka_subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) - send_post_notifications_subscription_comment_push_notification_call.reset_mock() + send_post_comment_push_notification_call.reset_mock() comment_text = make_fake_post_comment_text() @@ -2200,7 +2197,16 @@ def test_commenting_on_post_doesnt_send_post_subscription_push_notification_when url = self._get_url(post) self.client.put(url, data, **headers) - send_post_notifications_subscription_comment_push_notification_call.assert_not_called() + post_comment = PostComment.objects.get( + commenter_id=blocked_user.pk, + post_id=post.id + ) + + send_post_comment_push_notification_call.assert_called_once() + send_post_comment_push_notification_call.assert_called_with( + target_user=post_creator, + post_comment=post_comment + ) def test_comment_in_community_post_does_not_create_foreign_user_post_subscription_notification_when_closed(self): """ @@ -2216,7 +2222,7 @@ def test_comment_in_community_post_does_not_create_foreign_user_post_subscriptio foreign_user.join_community_with_name(community_name=community.name) # subscribe to notifications - foreign_user.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + foreign_user.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) # post will be closed now post.is_closed = True post.save() @@ -2228,7 +2234,7 @@ def test_comment_in_community_post_does_not_create_foreign_user_post_subscriptio url = self._get_url(post) self.client.put(url, data, **headers) - self.assertFalse(PostSubscriptionCommentNotification.objects.filter(post_comment__text=comment_text, + self.assertFalse(PostCommentNotification.objects.filter(post_comment__text=comment_text, notification__owner=foreign_user).exists()) def test_comment_in_community_post_by_admin_does_create_subscription_notification_to_another_admin_when_closed(self): @@ -2246,7 +2252,7 @@ def test_comment_in_community_post_by_admin_does_create_subscription_notificatio post = post_creator.create_community_post(text=make_fake_post_text(), community_name=community.name) # subscribe to notifications - admin.enable_post_subscription_comment_notifications_for_post_with_id(post_id=post.id) + admin.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) # post will be closed now post.is_closed = True post.save() @@ -2258,7 +2264,7 @@ def test_comment_in_community_post_by_admin_does_create_subscription_notificatio url = self._get_url(post) self.client.put(url, data, **headers) - self.assertTrue(PostSubscriptionCommentNotification.objects.filter(post_comment__text=comment_text, + self.assertTrue(PostCommentNotification.objects.filter(post_comment__text=comment_text, notification__owner=admin).exists()) def _get_create_post_comment_request_data(self, post_comment_text): From fbc24765a9eb01ed2e5ca67f4f1a6c2763cf126e Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 24 Mar 2020 15:37:36 +0100 Subject: [PATCH 22/40] :recycle: refactor view api --- openbook_communities/views/community/posts/serializers.py | 6 ++---- openbook_hashtags/views/hashtag/serializers.py | 5 +---- openbook_posts/views/post/serializers.py | 6 +----- openbook_posts/views/posts/serializers.py | 4 +--- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/openbook_communities/views/community/posts/serializers.py b/openbook_communities/views/community/posts/serializers.py index f3048add..9c19345f 100644 --- a/openbook_communities/views/community/posts/serializers.py +++ b/openbook_communities/views/community/posts/serializers.py @@ -6,7 +6,7 @@ CommonPostReactionSerializer, CommonPostLanguageSerializer, CommonHashtagSerializer from openbook_common.serializers_fields.community import CommunityPostsCountField from openbook_common.serializers_fields.post import PostReactionsEmojiCountField, CommentsCountField, PostCreatorField, \ - PostIsMutedField, ReactionField, PostSubscriptionCommentNotificationsEnabledField + PostIsMutedField, ReactionField from openbook_common.serializers_fields.request import RestrictedImageFileSizeField from openbook_communities.models import Community from openbook_communities.validators import community_name_characters_validator, community_name_exists @@ -52,7 +52,6 @@ class CommunityPostSerializer(serializers.ModelSerializer): reaction = ReactionField(reaction_serializer=CommonPostReactionSerializer) language = CommonPostLanguageSerializer() hashtags = CommonHashtagSerializer(many=True) - post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -76,8 +75,7 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', - 'hashtags', - 'post_subscription_comment_notifications_enabled' + 'hashtags' ) diff --git a/openbook_hashtags/views/hashtag/serializers.py b/openbook_hashtags/views/hashtag/serializers.py index ca06f2eb..b7c376de 100644 --- a/openbook_hashtags/views/hashtag/serializers.py +++ b/openbook_hashtags/views/hashtag/serializers.py @@ -7,8 +7,7 @@ CommonEmojiSerializer from openbook_common.serializers_fields.hashtag import HashtagPostsCountField, IsHashtagReportedField from openbook_common.serializers_fields.post import ReactionField, CommentsCountField, PostCreatorField, \ - PostReactionsEmojiCountField, PostIsMutedField, IsEncircledField, CirclesField, \ - PostSubscriptionCommentNotificationsEnabledField + PostReactionsEmojiCountField, PostIsMutedField, IsEncircledField, CirclesField from openbook_hashtags.models import Hashtag from openbook_hashtags.validators import hashtag_name_exists from openbook_posts.models import Post @@ -66,7 +65,6 @@ class GetHashtagPostsPostSerializer(serializers.ModelSerializer): hashtags = CommonHashtagSerializer(many=True) is_encircled = IsEncircledField() circles = CirclesField(circle_serializer=CommonCircleSerializer) - post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -89,7 +87,6 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', - 'post_subscription_comment_notifications_enabled', 'hashtags', 'circles' ) diff --git a/openbook_posts/views/post/serializers.py b/openbook_posts/views/post/serializers.py index 57fe36d8..8b7b591a 100644 --- a/openbook_posts/views/post/serializers.py +++ b/openbook_posts/views/post/serializers.py @@ -6,7 +6,7 @@ from openbook_common.models import Badge, Language from openbook_common.serializers import CommonHashtagSerializer from openbook_common.serializers_fields.post import PostCreatorField, PostReactionsEmojiCountField, ReactionField, \ - CommentsCountField, CirclesField, PostIsMutedField, IsEncircledField, PostSubscriptionCommentNotificationsEnabledField + CommentsCountField, CirclesField, PostIsMutedField, IsEncircledField from openbook_communities.models import CommunityMembership, Community from openbook_communities.serializers_fields import CommunityMembershipsField from openbook_posts.models import PostImage, Post, PostNotificationsSubscription @@ -153,7 +153,6 @@ class GetPostPostSerializer(serializers.ModelSerializer): language = PostLanguageSerializer() is_encircled = IsEncircledField() hashtags = CommonHashtagSerializer(many=True) - post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -175,7 +174,6 @@ class Meta: 'is_muted', 'is_edited', 'is_closed', - 'post_subscription_comment_notifications_enabled', 'is_encircled', 'media_height', 'media_width', @@ -305,14 +303,12 @@ class Meta: class PostNotificationsSubscriptionSettingsPostSerializer(serializers.ModelSerializer): - post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post fields = ( 'id', 'uuid', - 'post_subscription_comment_notifications_enabled', ) diff --git a/openbook_posts/views/posts/serializers.py b/openbook_posts/views/posts/serializers.py index 636433ac..75b94b4d 100644 --- a/openbook_posts/views/posts/serializers.py +++ b/openbook_posts/views/posts/serializers.py @@ -8,7 +8,7 @@ from openbook_common.models import Emoji, Badge from openbook_common.serializers import CommonHashtagSerializer from openbook_common.serializers_fields.post import ReactionField, CommentsCountField, PostReactionsEmojiCountField, \ - CirclesField, PostCreatorField, PostIsMutedField, IsEncircledField, PostSubscriptionCommentNotificationsEnabledField + CirclesField, PostCreatorField, PostIsMutedField, IsEncircledField from openbook_common.serializers_fields.request import RestrictedImageFileSizeField, RestrictedFileSizeField from openbook_common.models import Language from openbook_communities.models import Community, CommunityMembership @@ -234,7 +234,6 @@ class AuthenticatedUserPostSerializer(serializers.ModelSerializer): language = PostLanguageSerializer() is_encircled = IsEncircledField() hashtags = CommonHashtagSerializer(many=True) - post_subscription_comment_notifications_enabled = PostSubscriptionCommentNotificationsEnabledField() class Meta: model = Post @@ -259,7 +258,6 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', - 'post_subscription_comment_notifications_enabled', 'hashtags', ) From 021e8175c2d209c2f41257136dc6556a4b040cc2 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 25 Mar 2020 15:59:23 +0100 Subject: [PATCH 23/40] :recycle: change reaction notifications flow to use post subscriptions, same for comments --- openbook_auth/checkers.py | 14 +++++++++ openbook_auth/models.py | 46 +++++++++++++++++++++++------- openbook_posts/views/post/views.py | 19 ------------ 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index 4a01ea2b..3367a770 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -367,6 +367,20 @@ def check_can_create_or_update_post_notifications_subscription_for_post(user, po ) +def check_can_update_reaction_notifications_for_post(user, post): + if not user.id == post.creator.id: + raise ValidationError( + _('Only the post creator can modify reaction notifications'), + ) + + +def check_can_update_comment_reaction_notifications_for_post(user, post): + if not user.has_commented_post_with_id(post_id=post.pk): + raise ValidationError( + _('Only users with comments can modify comment reaction notifications'), + ) + + def check_can_create_or_update_notifications_subscription_for_post_comment(user, post_comment): if not user.can_see_post_comment(post_comment=post_comment): raise ValidationError( diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 7aaa2b06..bb4cfcc3 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -674,6 +674,9 @@ def has_muted_post_comment_with_id(self, post_comment_id): def has_disabled_comment_notifications_for_post(self, post_id): return self.post_notifications_subscriptions.filter(post_id=post_id, comment_notifications=False).exists() + def has_disabled_reaction_notifications_for_post_with_id(self, post_id): + return self.post_notifications_subscriptions.filter(post_id=post_id, reaction_notifications=False).exists() + def has_disabled_reply_notifications_for_post(self, post_id, post_comment_id): has_replied = self.has_replied_on_comment_with_id(post_comment_id=post_comment_id) is_commenter = self.posts_comments.filter(id=post_comment_id).exists() @@ -687,6 +690,10 @@ def has_disabled_reply_notifications_for_post_comment(self, post_comment_id): return self.post_comment_notifications_subscriptions.filter(post_comment_id=post_comment_id, reply_notifications=False).exists() + def has_disabled_reaction_notifications_for_post_comment(self, post_comment): + return self.post_comment_notifications_subscriptions.filter(post_comment_id=post_comment.id, + reaction_notifications=False).exists() + def has_blocked_user_with_id(self, user_id): return self.user_blocks.filter(blocked_user_id=user_id).exists() @@ -812,13 +819,19 @@ def has_post_mention_notifications_enabled(self): return self.notifications_settings.post_user_mention_notifications def has_reaction_notifications_enabled_for_post_with_id(self, post_id): - return self.notifications_settings.post_reaction_notifications and not self.has_muted_post_with_id( - post_id=post_id) + # @TODO :remove legacy has_muted_post_with_id check later + + return self.notifications_settings.post_reaction_notifications and \ + not self.has_muted_post_with_id(post_id=post_id) and \ + not self.has_disabled_reaction_notifications_for_post_with_id(post_id=post_id) def has_reaction_notifications_enabled_for_post_comment(self, post_comment): - return self.notifications_settings.post_comment_reaction_notifications and not self.has_muted_post_with_id( - post_id=post_comment.post_id) and not self.has_muted_post_comment_with_id( - post_comment_id=post_comment.id) + # @TODO :remove legacy has_muted_* checks later + return self.notifications_settings.post_comment_reaction_notifications and \ + not self.has_muted_post_with_id(post_id=post_comment.post_id) and \ + not self.has_muted_post_comment_with_id(post_comment_id=post_comment.id) and \ + not self.has_disabled_reaction_notifications_for_post_with_id(post_id=post_comment.post.pk) and \ + not self.has_disabled_reaction_notifications_for_post_comment(post_comment=post_comment) def has_comment_notifications_enabled_for_post_with_id(self, post_id): return self.notifications_settings.post_comment_notifications and \ @@ -1073,7 +1086,7 @@ def react_to_post(self, post, emoji_id): if post.creator.has_reaction_notifications_enabled_for_post_with_id(post_id=post.pk) and \ not post.creator.has_blocked_user_with_id(self.pk): self._create_post_reaction_notification(post_reaction=post_reaction) - self._send_post_reaction_push_notification(post_reaction=post_reaction) + self._send_post_reaction_push_notification(post_reaction=post_reaction) return post_reaction @@ -1103,12 +1116,13 @@ def react_to_post_comment(self, post_comment, emoji_id): else: post_comment_reaction = post_comment.react(reactor=self, emoji_id=emoji_id) if post_comment_reaction.post_comment.commenter_id != self.pk: - commenter_has_reaction_notifications_enabled = post_comment.commenter.has_reaction_notifications_enabled_for_post_comment( - post_comment=post_comment) + commenter_has_reaction_notifications_enabled = \ + post_comment.commenter.has_reaction_notifications_enabled_for_post_comment(post_comment=post_comment) - if commenter_has_reaction_notifications_enabled: + if commenter_has_reaction_notifications_enabled and \ + not post_comment.commenter.has_blocked_user_with_id(self.pk): self._send_post_comment_reaction_push_notification(post_comment_reaction=post_comment_reaction) - self._create_post_comment_reaction_notification(post_comment_reaction=post_comment_reaction) + self._create_post_comment_reaction_notification(post_comment_reaction=post_comment_reaction) return post_comment_reaction @@ -2245,6 +2259,12 @@ def create_post_notifications_subscription_for_post_with_id(self, post_id, check_can_create_or_update_post_notifications_subscription_for_post(user=self, post=post) + if reaction_notifications is not None: + check_can_update_reaction_notifications_for_post(user=self, post=post) + + if comment_reaction_notifications is not None: + check_can_update_comment_reaction_notifications_for_post(user=self, post=post) + post_notifications_subscription = PostNotificationsSubscription.\ create_post_notifications_subscription( post=post, @@ -2270,6 +2290,12 @@ def update_post_notifications_subscription_for_post_with_id(self, post_id, check_can_create_or_update_post_notifications_subscription_for_post(user=self, post=post) + if reaction_notifications is not None: + check_can_update_reaction_notifications_for_post(user=self, post=post) + + if comment_reaction_notifications is not None: + check_can_update_comment_reaction_notifications_for_post(user=self, post=post) + post_notifications_subscription = PostNotificationsSubscription.\ get_or_create_post_notifications_subscription(post=post, subscriber=self) diff --git a/openbook_posts/views/post/views.py b/openbook_posts/views/post/views.py index 50396f47..1970ee47 100644 --- a/openbook_posts/views/post/views.py +++ b/openbook_posts/views/post/views.py @@ -389,22 +389,3 @@ def patch(self, request, post_uuid): context={"request": request}) return Response(response_serializer.data, status=status.HTTP_200_OK) - - # def delete(self, request, post_uuid): - # request_data = request.data.copy() - # request_data['post_uuid'] = post_uuid - # - # serializer = PostNotificationsSubscriptionSettingsSerializer(data=request_data) - # serializer.is_valid(raise_exception=True) - # data = serializer.validated_data - # - # user = request.user - # post_uuid = data.get('post_uuid') - # post_id = get_post_id_for_post_uuid(post_uuid) - # - # with transaction.atomic(): - # post = user.disable_post_subscription_comment_notifications_for_post_with_id(post_id=post_id) - # - # response_serializer = PostNotificationsSubscriptionSettingsPostSerializer(post, context={"request": request}) - # - # return Response(response_serializer.data, status=status.HTTP_200_OK) From 836b10732e902d5a3d531103e09ffef4f61aafef Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 25 Mar 2020 15:59:39 +0100 Subject: [PATCH 24/40] :white_check_mark: update tests to use new mute method --- .../tests/views/test_post_comment_reactions.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openbook_posts/tests/views/test_post_comment_reactions.py b/openbook_posts/tests/views/test_post_comment_reactions.py index b9fa84c7..df65c267 100644 --- a/openbook_posts/tests/views/test_post_comment_reactions.py +++ b/openbook_posts/tests/views/test_post_comment_reactions.py @@ -669,7 +669,12 @@ def test_reacting_on_foreign_post_comment_doesnt_send_push_notification_when_mut headers = make_authentication_headers_for_user(reactor) post = user.create_public_post(text=make_fake_post_text()) post_comment = user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - user.mute_post_comment_with_id(post_comment_id=post_comment.pk) + + # mute post comment + user.update_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.pk, + reaction_notifications=False, + reply_notifications=False) emoji_group = make_reactions_emoji_group() @@ -694,7 +699,12 @@ def test_reacting_on_foreign_post_comment_doesnt_send_push_notification_when_pos headers = make_authentication_headers_for_user(reactor) post = user.create_public_post(text=make_fake_post_text()) post_comment = user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - user.mute_post_with_id(post_id=post.pk) + + # mute post notifications + user.update_post_notifications_subscription_for_post_with_id( + post_id=post.pk, + reaction_notifications=False, + comment_reaction_notifications=False) emoji_group = make_reactions_emoji_group() From 8a83c853e75ccc01ff21dd28888831a1c9f2dba3 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 31 Mar 2020 12:32:10 +0200 Subject: [PATCH 25/40] :white_check_mark: fix soem tests --- openbook_auth/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index bb4cfcc3..33a5eace 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -2209,8 +2209,8 @@ def close_post(self, post): return post def create_post_comment_notifications_subscription_for_comment_with_id(self, post_comment_id, - reply_notifications=False, - reaction_notifications=False): + reply_notifications=None, + reaction_notifications=None): PostComment = get_post_comment_model() post_comment = PostComment.objects.get(id=post_comment_id) PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() @@ -2228,8 +2228,8 @@ def create_post_comment_notifications_subscription_for_comment_with_id(self, pos return post_comment_notifications_subscription def update_post_comment_notifications_subscription_for_comment_with_id(self, post_comment_id, - reply_notifications=None, - reaction_notifications=None): + reply_notifications=False, + reaction_notifications=False): PostComment = get_post_comment_model() post_comment = PostComment.objects.get(id=post_comment_id) @@ -2259,10 +2259,10 @@ def create_post_notifications_subscription_for_post_with_id(self, post_id, check_can_create_or_update_post_notifications_subscription_for_post(user=self, post=post) - if reaction_notifications is not None: + if reaction_notifications is True: check_can_update_reaction_notifications_for_post(user=self, post=post) - if comment_reaction_notifications is not None: + if comment_reaction_notifications is True: check_can_update_comment_reaction_notifications_for_post(user=self, post=post) post_notifications_subscription = PostNotificationsSubscription.\ From fa03dda8ab7896a78252cf402ad0ca39a8d66e4a Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 1 Apr 2020 12:55:25 +0200 Subject: [PATCH 26/40] :white_check_mark: more fixin tests --- openbook_auth/models.py | 15 ++- openbook_posts/tests/views/test_post.py | 142 +++++++++++++++-------- openbook_posts/views/post/serializers.py | 12 ++ openbook_posts/views/post/views.py | 6 +- 4 files changed, 118 insertions(+), 57 deletions(-) diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 33a5eace..16069bf1 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -2209,8 +2209,8 @@ def close_post(self, post): return post def create_post_comment_notifications_subscription_for_comment_with_id(self, post_comment_id, - reply_notifications=None, - reaction_notifications=None): + reply_notifications=False, + reaction_notifications=False): PostComment = get_post_comment_model() post_comment = PostComment.objects.get(id=post_comment_id) PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() @@ -2228,8 +2228,8 @@ def create_post_comment_notifications_subscription_for_comment_with_id(self, pos return post_comment_notifications_subscription def update_post_comment_notifications_subscription_for_comment_with_id(self, post_comment_id, - reply_notifications=False, - reaction_notifications=False): + reply_notifications=None, + reaction_notifications=None): PostComment = get_post_comment_model() post_comment = PostComment.objects.get(id=post_comment_id) @@ -2290,16 +2290,15 @@ def update_post_notifications_subscription_for_post_with_id(self, post_id, check_can_create_or_update_post_notifications_subscription_for_post(user=self, post=post) + post_notifications_subscription = PostNotificationsSubscription. \ + get_or_create_post_notifications_subscription(post=post, + subscriber=self) if reaction_notifications is not None: check_can_update_reaction_notifications_for_post(user=self, post=post) if comment_reaction_notifications is not None: check_can_update_comment_reaction_notifications_for_post(user=self, post=post) - post_notifications_subscription = PostNotificationsSubscription.\ - get_or_create_post_notifications_subscription(post=post, - subscriber=self) - post_notifications_subscription.update( comment_notifications=comment_notifications, comment_reaction_notifications=comment_reaction_notifications, diff --git a/openbook_posts/tests/views/test_post.py b/openbook_posts/tests/views/test_post.py index 98f7f1f2..04b7cf84 100644 --- a/openbook_posts/tests/views/test_post.py +++ b/openbook_posts/tests/views/test_post.py @@ -27,7 +27,7 @@ from openbook_common.utils.model_loaders import get_language_model, get_community_new_post_notification_model, \ get_post_comment_notification_model, get_post_comment_user_mention_notification_model, \ get_post_user_mention_notification_model, get_post_comment_reaction_notification_model, \ - get_post_comment_reply_notification_model + get_post_comment_reply_notification_model, get_post_notifications_subscription_model from openbook_communities.models import Community from openbook_hashtags.models import Hashtag from openbook_notifications.models import PostUserMentionNotification, Notification @@ -1322,9 +1322,11 @@ def test_can_subscribe_comment_notifications_for_public_post(self): headers = make_authentication_headers_for_user(subscriber) post = user.create_public_post(text=make_fake_post_text()) + data = { + 'comment_notifications': True + } url = self._get_url(post) - - response = self.client.put(url, **headers) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue(subscriber.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1342,9 +1344,11 @@ def test_cannot_subscribe_to_comment_notifications_for_post_if_encircled_post(se post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + data = { + 'comment_notifications': True + } url = self._get_url(post) - - response = self.client.put(url, **headers) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -1365,9 +1369,13 @@ def test_can_subscribe_to_comment_notifications_for_post_if_part_of_encircled_po foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) + data = { + 'comment_notifications': True + } + url = self._get_url(post) - response = self.client.put(url, **headers) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -1386,9 +1394,12 @@ def test_can_subscribe_to_comment_notifications_for_community_post_if_public(sel user.join_community_with_name(community_name=community.name) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + data = { + 'comment_notifications': True + } url = self._get_url(post) - response = self.client.put(url, **headers) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -1409,9 +1420,12 @@ def test_cannot_subscribe_to_comment_notifications_for_closed_community_post_if_ post.is_closed = True post.save() - url = self._get_url(post) + data = { + 'comment_notifications': True + } - response = self.client.put(url, **headers) + url = self._get_url(post) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -1432,9 +1446,12 @@ def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_cre post.is_closed = True post.save() - url = self._get_url(post) + data = { + 'comment_notifications': True + } - response = self.client.put(url, **headers) + url = self._get_url(post) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1454,9 +1471,12 @@ def test_can_subscribe_to_comment_notifications_for_closed_community_post_admini post.is_closed = True post.save() - url = self._get_url(post) + data = { + 'comment_notifications': True + } - response = self.client.put(url, **headers) + url = self._get_url(post) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue(admin.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1480,9 +1500,11 @@ def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_mod post.is_closed = True post.save() + data = { + 'comment_notifications': True + } url = self._get_url(post) - - response = self.client.put(url, **headers) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue(moderator.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1499,9 +1521,11 @@ def test_cannot_subscribe_to_comment_notifications_for_community_post_if_private headers = make_authentication_headers_for_user(user) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + data = { + 'comment_notifications': True + } url = self._get_url(post) - - response = self.client.put(url, **headers) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -1524,9 +1548,11 @@ def test_can_subscribe_to_comment_notifications_for_community_post_if_private_an user.join_community_with_name(community_name=community.name) + data = { + 'comment_notifications': True + } url = self._get_url(post) - - response = self.client.put(url, **headers) + response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1540,14 +1566,19 @@ def test_can_unsubscribe_comment_notifications_for_public_post(self): headers = make_authentication_headers_for_user(subscriber) post = user.create_public_post(text=make_fake_post_text()) - subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) + data = { + 'comment_notifications': False + } url = self._get_url(post) + response = self.client.patch(url, data, **headers) - response = self.client.delete(url, **headers) + PostNotificationsSubscription = get_post_notifications_subscription_model() + post_notification_subscription = PostNotificationsSubscription.objects.get(post=post, subscriber=subscriber) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + self.assertFalse(post_notification_subscription.comment_notifications) def test_cannot_unsubscribe_to_comment_notifications_for_post_if_encircled_post(self): """ @@ -1562,9 +1593,11 @@ def test_cannot_unsubscribe_to_comment_notifications_for_post_if_encircled_post( post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1583,11 +1616,13 @@ def test_can_unsubscribe_to_comment_notifications_for_post_if_part_of_encircled_ post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) - user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1604,11 +1639,13 @@ def test_can_unsubscribe_to_comment_notifications_for_community_post_if_public(s headers = make_authentication_headers_for_user(user) user.join_community_with_name(community_name=community.name) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) - user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1625,13 +1662,16 @@ def test_cannot_unsubscribe_to_comment_notifications_for_closed_community_post_i headers = make_authentication_headers_for_user(user) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) - user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) post.is_closed = True post.save() + data = { + 'comment_notifications': False + } url = self._get_url(post) - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1648,13 +1688,15 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_c headers = make_authentication_headers_for_user(user) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) - user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) post.is_closed = True post.save() + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1671,13 +1713,15 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_admi headers = make_authentication_headers_for_user(admin) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) - admin.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + admin.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) post.is_closed = True post.save() + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(admin.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1698,13 +1742,15 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_m headers = make_authentication_headers_for_user(moderator) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) - moderator.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + moderator.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) post.is_closed = True post.save() + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(moderator.are_post_subscription_comment_notifications_enabled_for_post(post=post)) @@ -1721,9 +1767,11 @@ def test_cannot_unsubscribe_to_comment_notifications_for_community_post_if_priva headers = make_authentication_headers_for_user(user) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -1745,11 +1793,13 @@ def test_can_unsubscribe_to_comment_notifications_for_community_post_if_private_ community_name=community.name) user.join_community_with_name(community_name=community.name) - user.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + user.create_post_notifications_subscription_for_post_with_id(post_id=post.id, comment_notifications=True) + data = { + 'comment_notifications': False + } url = self._get_url(post) - - response = self.client.delete(url, **headers) + response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) diff --git a/openbook_posts/views/post/serializers.py b/openbook_posts/views/post/serializers.py index 8b7b591a..4806c5cd 100644 --- a/openbook_posts/views/post/serializers.py +++ b/openbook_posts/views/post/serializers.py @@ -225,6 +225,18 @@ class PostNotificationsSubscriptionSettingsSerializer(serializers.Serializer): reply_where_commented_notifications = serializers.BooleanField(required=False) +class UpdatePostNotificationsSubscriptionSettingsSerializer(serializers.Serializer): + post_uuid = serializers.UUIDField( + validators=[post_uuid_exists], + required=True, + ) + comment_notifications = serializers.NullBooleanField(required=False, default=None) + comment_reaction_notifications = serializers.NullBooleanField(required=False, default=None) + reaction_notifications = serializers.NullBooleanField(required=False, default=None) + reply_notifications = serializers.NullBooleanField(required=False, default=None) + reply_where_commented_notifications = serializers.NullBooleanField(required=False, default=None) + + class OpenPostSerializer(serializers.Serializer): post_uuid = serializers.UUIDField( validators=[post_uuid_exists], diff --git a/openbook_posts/views/post/views.py b/openbook_posts/views/post/views.py index 1970ee47..98f44298 100644 --- a/openbook_posts/views/post/views.py +++ b/openbook_posts/views/post/views.py @@ -14,8 +14,8 @@ OpenClosePostSerializer, \ OpenPostSerializer, ClosePostSerializer, TranslatePostSerializer, \ SearchPostParticipantsSerializer, PostParticipantSerializer, GetPostParticipantsSerializer, PublishPostSerializer, \ - GetPostStatusSerializer, PostNotificationsSubscriptionSettingsSerializer, \ - PostNotificationsSubscriptionSettingsPostSerializer, PostNotificationsSubscriptionSettingsResponseSerializer + GetPostStatusSerializer, PostNotificationsSubscriptionSettingsSerializer, PostNotificationsSubscriptionSettingsResponseSerializer, \ + UpdatePostNotificationsSubscriptionSettingsSerializer from openbook_translation.strategies.base import TranslationClientError, UnsupportedLanguagePairException, \ MaxTextLengthExceededError @@ -362,7 +362,7 @@ def patch(self, request, post_uuid): request_data = request.data.copy() request_data['post_uuid'] = post_uuid - serializer = PostNotificationsSubscriptionSettingsSerializer(data=request_data) + serializer = UpdatePostNotificationsSubscriptionSettingsSerializer(data=request_data) serializer.is_valid(raise_exception=True) user = request.user From 6a6cb5de9b23d4793f04a15e15e2b7033c76d45b Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 1 Apr 2020 13:21:20 +0200 Subject: [PATCH 27/40] :lock: fix safety upgrade django --- Pipfile | 2 +- Pipfile.lock | 507 +++++++++++++++++++++++++---------------------- requirements.txt | 99 ++++----- 3 files changed, 329 insertions(+), 279 deletions(-) diff --git a/Pipfile b/Pipfile index 417a9351..4aab22ff 100644 --- a/Pipfile +++ b/Pipfile @@ -8,7 +8,7 @@ polib = "*" pytz = "*" [packages] -django = "==2.2.5" +django = "==2.2.10" python-dotenv = "*" djangorestframework = "*" sentry-sdk = "==0.10.2" diff --git a/Pipfile.lock b/Pipfile.lock index ed1a386e..8484da99 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "98e3fb6b57d9264c03c482695f9315ed74639819cf4dc4333069cc1a514c17b4" + "sha256": "e1ac725bd216da923267b8c5f03ce18edcfbf2aa7956ea641a8164a10d26a83c" }, "pipfile-spec": 6, "requires": {}, @@ -21,13 +21,6 @@ ], "version": "==1.4.3" }, - "argh": { - "hashes": [ - "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3", - "sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65" - ], - "version": "==0.26.2" - }, "bandit": { "hashes": [ "sha256:6102b5d6afd9d966df5054e0bdfc2e73a24d0fea400ec25f2e54c134412158d7", @@ -38,27 +31,27 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:5279c36b4b2ec2cb4298d723791467e3000e5384a43ea0cdf5d45207c7e97169", - "sha256:6135db2ba678168c07950f9a16c4031822c6f4aec75a65e0a97bc5ca09789931", - "sha256:dcdef580e18a76d54002088602eba453eec38ebbcafafeaabd8cab12b6155d57" + "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a", + "sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887", + "sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae" ], "index": "pypi", - "version": "==4.8.1" + "version": "==4.8.2" }, "boto3": { "hashes": [ - "sha256:bbfc33584b10a1d245db8538f7fa728f54f35a953eb7dc74037ed8401acd309f", - "sha256:d9da982bfe358d6ebc21177035c0f6489e356a1b5a80bb90b8b2b9d787ff8fb6" + "sha256:6f239e3798a1e4d5c9a8093b80c85110d1f872d4f61ea348aa89269b14da186f", + "sha256:ad774b0e98d093ae425b85fdb25e5ae70a02ebe036c4222e1b93444699719221" ], "index": "pypi", - "version": "==1.10.29" + "version": "==1.12.33" }, "botocore": { "hashes": [ - "sha256:37d2381ecd3843a4f5f2dbf86411ba834baf23988c9824d607b6cf6fbd684583", - "sha256:ef1cc8fe86c4a04d1d8832fffc2ba08150c8d1b30ce8bb62b93b5a69bde20f6d" + "sha256:078a9731e054f9ee914a8eac77e445de3c1344a00d7eebfee9cae7f40b7f48e8", + "sha256:4240f2fb38c4cccc0fd319e6620a30a1c19b869c1e33ff864eec9cde7965a6ba" ], - "version": "==1.13.29" + "version": "==1.15.33" }, "certifi": { "hashes": [ @@ -76,17 +69,17 @@ }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", + "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" ], - "version": "==7.0" + "version": "==7.1.1" }, "colorama": { "hashes": [ - "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", - "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "version": "==0.4.1" + "version": "==0.4.3" }, "colormath": { "hashes": [ @@ -96,45 +89,47 @@ }, "coverage": { "hashes": [ - "sha256:2358e685d0253125da42a48396038d4c7b4cd1448c00bbc4bda0cb8c43c2a870", - "sha256:25017cf384eeed2e6caf72efd3ec4124e32a8b7a4387600499104387975400c7", - "sha256:2e2de9423ff8b14303a97eafddd16c479fbcc9a0b8b0be3b7c3843a3e0cf6d69", - "sha256:324ed908e4e40a6e2451056fe502470ad4e79495cb7a03ecab94e6309c3e117e", - "sha256:34f865a0cf6255b694a46e4383a7131c61ea72c5b4c4f81d20e522fb1e440b4b", - "sha256:3a2bcc464b60a18f1f7167b95b2773ede93bf3722bfa59e0802717f652b6cc25", - "sha256:48d70865266d649b6602e2ba94820d7972ef470d3b72a8fd41a3d17321feed3a", - "sha256:50cf23523ab3a724c6905d3b60f87fa8250d9bae3995e09f49f63effa2b54f15", - "sha256:54c84a68abd8c4c5b71878b35eb85321df41f3d144c78181867d5b026ec74994", - "sha256:5b59d661ee7f3200aedd7b71882b7927ea7ed522df75e3853f316a79ad872a2e", - "sha256:5ffb39624bc573177888a21fb301ccee46838c600b27d58c3e9dae495f44d34a", - "sha256:699b3072b7f0e69ed175a88fa8b2ec7eefc4f34d490c54ed9a52feff21a15fdc", - "sha256:79ef4a2bb862110bd585174e551a783bee5c3aa461734a2ac7429193be357589", - "sha256:8210a6f93c4a8c6d460b402e20e38399529b99200c3318542faf6a520c9b6a5c", - "sha256:8d30c10cfd0a6fdf0a2d5023de00ef7b329cd6ead2310c9e53eab79c209acb70", - "sha256:97ac79ff28f2cda6ac00a803ee582b965951755f61ab43377482bfba450b619a", - "sha256:9fe4aacacff9028ed167db108bf013510654f148d83c4857fed61d2ce0588bf2", - "sha256:a5b6395d5957d638f8b1870561607e3c39b1a236ea6cff9eafe5b9bb1db913f2", - "sha256:ab32c5fad6905986a7e34e3acf01180a69bb60c2aa7331815b46e51c776a1943", - "sha256:ad67f0cfdfecbd49b9da46a7e488e6dc32a69388740b85c36a4ef4b33082cbad", - "sha256:aedad67c30326a1af324f45833a40b97180664912deb29942459ddbe9fa0ce19", - "sha256:b077cd0e70f41366ac1f9d09275258fa1906758a5d4f31cacc18b10dfcf90784", - "sha256:b8ea210810d3c14aec7561f8fe0d3eec582d1088100aaa0bb8153d53d867d20f", - "sha256:bf572722326ce6704e863447a070039a827072b7179352570859be899b9e6551", - "sha256:c0df57e189dacd2606cae6386acf127d01d85b2bf49acd9a65543b5d6c359ddc", - "sha256:d523e75f2a8a0b4a6a8be1287c0e0e3a561b8832b05ddd987d4cd7c62f3ad3bc", - "sha256:e10593c60c5f0bfd8b241bf9f27ef2191a3005b73dde8ada0424f642543a1e59", - "sha256:e9128444c83bc260aea988bf1ca6278a33ba730955bf94720468c656b61353eb", - "sha256:f7162f2e3711f3a08a8a741f92e1f63afd58d0713177979f2cf9723dd50161cf" - ], - "index": "pypi", - "version": "==5.0b1" + "sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0", + "sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30", + "sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b", + "sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0", + "sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823", + "sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe", + "sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037", + "sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6", + "sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31", + "sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd", + "sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892", + "sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1", + "sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78", + "sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac", + "sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006", + "sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014", + "sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2", + "sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7", + "sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8", + "sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7", + "sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9", + "sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1", + "sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307", + "sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a", + "sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435", + "sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0", + "sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5", + "sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441", + "sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732", + "sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de", + "sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1" + ], + "index": "pypi", + "version": "==5.0.4" }, "croniter": { "hashes": [ - "sha256:0d905dbe6f131a910fd3dde792f0129788cd2cb3a8048c5f7aaa212670b0cef2", - "sha256:538adeb3a7f7816c3cdec6db974c441620d764c25ff4ed0146ee7296b8a50590" + "sha256:8984b4b27ddfc4b95b2bcec17ee31f827426cf1d717c2af79eff4b4435e23197", + "sha256:cfd0837246845d3f3eb463ae8790bfb7db6ac76743e8f31aacaf37830de7fb52" ], - "version": "==0.3.30" + "version": "==0.3.31" }, "cursor": { "hashes": [ @@ -144,33 +139,39 @@ }, "decorator": { "hashes": [ - "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", - "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", + "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" ], - "version": "==4.4.1" + "version": "==4.4.2" + }, + "distlib": { + "hashes": [ + "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" + ], + "version": "==0.3.0" }, "django": { "hashes": [ - "sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa", - "sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56" + "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a", + "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038" ], "index": "pypi", - "version": "==2.2.5" + "version": "==2.2.10" }, "django-amazon-ses": { "hashes": [ - "sha256:2135a6ba2ac6b004f534afab17c812ce8c42db7bc64cc149f8f696fecc6925c3", - "sha256:fb168c60fb9bc7fc482cf664e861643816c181dccb9e875d6ecc61f76fc0b0b3" + "sha256:d0e79422584e35e24788f60190fb5a1c4809560572e78fd7380a2ae864704ad6", + "sha256:f39503d5d16c7788b0ac45d7781100074ff3a19858e5c754031cd2281c7e4658" ], "index": "pypi", - "version": "==2.1.1" + "version": "==3.0.2" }, "django-appconf": { "hashes": [ - "sha256:35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3", - "sha256:c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6" + "sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06", + "sha256:be58deb54a43d77d2e1621fe59f787681376d3cd0b8bd8e4758ef6c3a6453380" ], - "version": "==1.0.3" + "version": "==1.0.4" }, "django-cacheops": { "hashes": [ @@ -190,11 +191,11 @@ }, "django-extensions": { "hashes": [ - "sha256:a9db7c56a556d244184f589f2437b4228de86ee45e5ebb837fb20c6d54e95ea5", - "sha256:b58320d3fe3d6ae7d1d8e38959713fa92272f4921e662d689058d942a5b444f7" + "sha256:2f81b618ba4d1b0e58603e25012e5c74f88a4b706e0022a3b21f24f0322a6ce6", + "sha256:b19182d101a441fe001c5753553a901e2ef3ff60e8fbbe38881eb4a61fdd17c4" ], "index": "pypi", - "version": "==2.2.5" + "version": "==2.2.9" }, "django-imagekit": { "hashes": [ @@ -213,10 +214,10 @@ }, "django-model-utils": { "hashes": [ - "sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760", - "sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96" + "sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c", + "sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061" ], - "version": "==3.2.0" + "version": "==4.0.0" }, "django-modeltranslation": { "git": "https://github.com/lifenautjoe/django-modeltranslation.git", @@ -255,11 +256,11 @@ }, "django-redis": { "hashes": [ - "sha256:af0b393864e91228dd30d8c85b5c44d670b5524cb161b7f9e41acc98b6e5ace7", - "sha256:f46115577063d00a890867c6964ba096057f07cb756e78e0503b89cd18e4e083" + "sha256:a5b1e3ffd3198735e6c529d9bdf38ca3fcb3155515249b98dc4d966b8ddf9d2b", + "sha256:e1aad4cc5bd743d8d0b13d5cae0cef5410eaace33e83bff5fc3a139ad8db50b4" ], "index": "pypi", - "version": "==4.10.0" + "version": "==4.11.0" }, "django-replicated": { "hashes": [ @@ -270,11 +271,11 @@ }, "django-rq": { "hashes": [ - "sha256:1eda0efe0ad1638c5671412edc28a3574a9a69fdc567be56dc86a7a0a8687343", - "sha256:d9377e851336430676e5fb52c5efae379348355be84d5fc0ece70c0479dc0600" + "sha256:0c1a0e730dd71fdb6564ec830430148c2e26081fbb87c4553f8e205632e7dcc6", + "sha256:307308582b99dc41f663f48e587be9e7f843fa068a330577da8d415810868920" ], "index": "pypi", - "version": "==2.1.0" + "version": "==2.3.0" }, "django-rq-scheduler": { "hashes": [ @@ -286,19 +287,19 @@ }, "django-storages": { "hashes": [ - "sha256:0a9b7e620e969fb0797523695329ed223bf540bbfdf6cd163b061fc11dab2d1c", - "sha256:9322ab74ba6371e2e0fccc350c741686ade829e43085597b26b07ae8955a0a00" + "sha256:3103991c2ee8cef8a2ff096709973ffe7106183d211a79f22cf855f33533d924", + "sha256:a59e9923cbce7068792f75344ed7727021ee4ac20f227cf17297d0d03d141e91" ], "index": "pypi", - "version": "==1.8" + "version": "==1.9.1" }, "djangorestframework": { "hashes": [ - "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8", - "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090" + "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4", + "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f" ], "index": "pypi", - "version": "==3.10.3" + "version": "==3.11.0" }, "docutils": { "hashes": [ @@ -310,10 +311,10 @@ }, "dparse": { "hashes": [ - "sha256:00a5fdfa900629e5159bf3600d44905b333f4059a3366f28e0dbd13eeab17b19", - "sha256:cef95156fa0adedaf042cd42f9990974bec76f25dfeca4dc01f381a243d5aa5b" + "sha256:14fed5efc5e98c0a81dfe100c4c2ea0a4c189104e9a9d18b5cfd342a163f97be", + "sha256:db349e53f6d03c8ee80606c49b35f515ed2ab287a8e1579e2b4bdf52b12b1530" ], - "version": "==0.4.1" + "version": "==0.5.0" }, "faker": { "hashes": [ @@ -330,54 +331,70 @@ "index": "pypi", "version": "==0.2.2" }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, "funcy": { "hashes": [ "sha256:75ee84c3b446f92e68a857c2267b15a1b49c631c9d5a87a5f063cd2d6761a5c4" ], "version": "==1.14" }, - "gitdb2": { + "gitdb": { "hashes": [ - "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350", - "sha256:96bbb507d765a7f51eb802554a9cfe194a174582f772e0d89f4e87288c288b7b" + "sha256:284a6a4554f954d6e737cddcff946404393e030b76a282c6640df8efd6b3da5e", + "sha256:598e0096bb3175a0aab3a0b5aedaa18a9a25c6707e0eca0695ba1a0baf1b2150" ], - "version": "==2.0.6" + "version": "==4.0.2" }, "gitpython": { "hashes": [ - "sha256:9c2398ffc3dcb3c40b27324b316f08a4f93ad646d5a6328cafbb871aa79f5e42", - "sha256:c155c6a2653593ccb300462f6ef533583a913e17857cfef8fc617c246b6dc245" + "sha256:43da89427bdf18bf07f1164c6d415750693b4d50e28fc9b68de706245147b9dd", + "sha256:e426c3b587bd58c482f0b7fe6145ff4ac7ae6c82673fc656f489719abca6f4cb" ], - "version": "==3.0.5" + "version": "==3.1.0" }, "halo": { "hashes": [ - "sha256:9adbfe0a23ae7198a17895aac8845685eb0abeaa13c6a56b60410b347d0f503a", - "sha256:9ebf98b94a43f3b68e18c6d74dcb1ea58446b8457ce6fb1b2b4cac8d83733f80" + "sha256:67996649083229a9b7a93fe7c871c97d40bd2f1a988540eccf948a6b1844f644", + "sha256:a8aeb0164e269d7c96fb7444b2a4caaa09b8989fa0c85e6a26e8b2c6d1af3b9d" ], "index": "pypi", - "version": "==0.0.28" + "version": "==0.0.29" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" + }, + "importlib-metadata": { + "hashes": [ + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" + ], + "markers": "python_version < '3.8'", + "version": "==1.6.0" }, "jmespath": { "hashes": [ - "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", - "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" + "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec", + "sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9" ], - "version": "==0.9.4" + "version": "==0.9.5" }, "langdetect": { "hashes": [ - "sha256:91a170d5f0ade380db809b3ba67f08e95fe6c6c8641f96d67a51ff7e98a9bf30" + "sha256:363795ea005f1243c958e953245dac5d814fabdc025c9afa91588c5fa6b2fa83", + "sha256:f37495e63607865e47deed08d78f7f8e58172658216ff954b2f14671bcd87740" ], "index": "pypi", - "version": "==1.0.7" + "version": "==1.0.8" }, "log-symbols": { "hashes": [ @@ -429,29 +446,29 @@ }, "numpy": { "hashes": [ - "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", - "sha256:0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", - "sha256:3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", - "sha256:43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", - "sha256:475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", - "sha256:64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", - "sha256:683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", - "sha256:6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", - "sha256:75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", - "sha256:7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", - "sha256:8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", - "sha256:9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", - "sha256:a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", - "sha256:acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", - "sha256:ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", - "sha256:c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", - "sha256:e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", - "sha256:e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", - "sha256:ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", - "sha256:f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", - "sha256:fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5" - ], - "version": "==1.17.4" + "sha256:1598a6de323508cfeed6b7cd6c4efb43324f4692e20d1f76e1feec7f59013448", + "sha256:1b0ece94018ae21163d1f651b527156e1f03943b986188dd81bc7e066eae9d1c", + "sha256:2e40be731ad618cb4974d5ba60d373cdf4f1b8dcbf1dcf4d9dff5e212baf69c5", + "sha256:4ba59db1fcc27ea31368af524dcf874d9277f21fd2e1f7f1e2e0c75ee61419ed", + "sha256:59ca9c6592da581a03d42cc4e270732552243dc45e87248aa8d636d53812f6a5", + "sha256:5e0feb76849ca3e83dd396254e47c7dba65b3fa9ed3df67c2556293ae3e16de3", + "sha256:6d205249a0293e62bbb3898c4c2e1ff8a22f98375a34775a259a0523111a8f6c", + "sha256:6fcc5a3990e269f86d388f165a089259893851437b904f422d301cdce4ff25c8", + "sha256:82847f2765835c8e5308f136bc34018d09b49037ec23ecc42b246424c767056b", + "sha256:87902e5c03355335fc5992a74ba0247a70d937f326d852fc613b7f53516c0963", + "sha256:9ab21d1cb156a620d3999dd92f7d1c86824c622873841d6b080ca5495fa10fef", + "sha256:a1baa1dc8ecd88fb2d2a651671a84b9938461e8a8eed13e2f0a812a94084d1fa", + "sha256:a244f7af80dacf21054386539699ce29bcc64796ed9850c99a34b41305630286", + "sha256:a35af656a7ba1d3decdd4fae5322b87277de8ac98b7d9da657d9e212ece76a61", + "sha256:b1fe1a6f3a6f355f6c29789b5927f8bd4f134a4bd9a781099a7c4f66af8850f5", + "sha256:b5ad0adb51b2dee7d0ee75a69e9871e2ddfb061c73ea8bc439376298141f77f5", + "sha256:ba3c7a2814ec8a176bb71f91478293d633c08582119e713a0c5351c0f77698da", + "sha256:cd77d58fb2acf57c1d1ee2835567cd70e6f1835e32090538f17f8a3a99e5e34b", + "sha256:cdb3a70285e8220875e4d2bc394e49b4988bdb1298ffa4e0bd81b2f613be397c", + "sha256:deb529c40c3f1e38d53d5ae6cd077c21f1d49e13afc7936f7f868455e16b64a0", + "sha256:e7894793e6e8540dbeac77c87b489e331947813511108ae097f1715c018b8f3d" + ], + "version": "==1.18.2" }, "onesignal-sdk": { "hashes": [ @@ -463,10 +480,10 @@ }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==19.2" + "version": "==20.3" }, "pathtools": { "hashes": [ @@ -489,39 +506,31 @@ }, "pillow": { "hashes": [ - "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", - "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", - "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", - "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", - "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", - "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", - "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", - "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", - "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", - "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", - "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", - "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", - "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", - "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", - "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", - "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", - "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", - "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", - "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", - "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", - "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", - "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", - "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", - "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", - "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", - "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", - "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", - "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", - "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", - "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0" - ], - "index": "pypi", - "version": "==6.2.1" + "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be", + "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946", + "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837", + "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f", + "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00", + "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d", + "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533", + "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a", + "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358", + "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda", + "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435", + "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2", + "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313", + "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff", + "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317", + "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2", + "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614", + "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0", + "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386", + "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9", + "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636", + "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865" + ], + "index": "pypi", + "version": "==7.0.0" }, "pinocchio": { "hashes": [ @@ -530,6 +539,14 @@ "index": "pypi", "version": "==0.4.2" }, + "pipenv": { + "hashes": [ + "sha256:56ad5f5cb48f1e58878e14525a6e3129d4306049cb76d2f6a3e95df0d5fc6330", + "sha256:7df8e33a2387de6f537836f48ac6fcd94eda6ed9ba3d5e3fd52e35b5bc7ff49e", + "sha256:a673e606e8452185e9817a987572b55360f4d28b50831ef3b42ac3cab3fee846" + ], + "version": "==2018.11.26" + }, "pyjwt": { "hashes": [ "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", @@ -540,26 +557,25 @@ }, "pyparsing": { "hashes": [ - "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", - "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" + "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", + "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" ], - "version": "==2.4.5" + "version": "==2.4.6" }, "python-dateutil": { "hashes": [ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], - "markers": "python_version >= '2.7'", "version": "==2.8.1" }, "python-dotenv": { "hashes": [ - "sha256:debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093", - "sha256:f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544" + "sha256:81822227f771e0cab235a2939f0f265954ac4763cafd806d845801c863bf372f", + "sha256:92b3123fb2d58a284f76cc92bfe4ee6c502c32ded73e8b051c4f6afc8b6751ed" ], "index": "pypi", - "version": "==0.10.3" + "version": "==0.12.0" }, "python-magic": { "hashes": [ @@ -578,37 +594,35 @@ }, "pyyaml": { "hashes": [ - "sha256:05418379e70ae2e986d31cfb51b50bd0f93bf5ab9d9b40dabdb4616727c4c26e", - "sha256:37c31e6087df09321539c18b5b02382538354c350dc76f3b458a6c93745a545c", - "sha256:3fd57916529381a46619e1cbfe1d372c7e008d5945fb1953da4a03b195630c33", - "sha256:52559ed9a06e2775d5c7ec5d86932371a439d6594a21991589475894a399939b", - "sha256:79288cdd596f9b77687f9e363fabf74a71f0399034b2742a74b1ca1f0ba5285f", - "sha256:7f737a46c65635898a1cb19b2ddf4e0c906d3f2e422c995f828fb621f8fa856b", - "sha256:93c09bcfe50adc03bbfb74f665e680d984b1023e83d0b48c93f7e2a8e70ac4be", - "sha256:94641ee1659be00239882b74e824ca6bc6b0c42f3f63b772f8f42cfaacfc83ab", - "sha256:9af87170b8a6c8e3219139a7146835bde3f5532cc47553701829a111cb2a9313", - "sha256:b6bd554afb21407f503d7821b9b8a4c3e36d3eb3e8fee329aa138cb5bc4f4809", - "sha256:c41a87796b705a473db39d06c220b1f25616b8c92fb5ea5c7fe327d2dcd63eb3", - "sha256:f00cd88db394bab373bcdcc58ab2eb5c3c3e75a520c6fab084a66d0ecd8cf90c", - "sha256:f65ea27155c5401e493935abdb10929b6df66256f202b91d00e967e54411f3a0" + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" ], "index": "pypi", - "version": "==5.2b1" + "version": "==5.3.1" }, "redis": { "hashes": [ - "sha256:3613daad9ce5951e426f460deddd5caf469e08a3af633e9578fc77d362becf62", - "sha256:8d0fc278d3f5e1249967cba2eb4a5632d19e45ce5c09442b8422d15ee2c22cc2" + "sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f", + "sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833" ], "index": "pypi", - "version": "==3.3.11" + "version": "==3.4.1" }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "requests-file": { "hashes": [ @@ -623,10 +637,10 @@ }, "rq": { "hashes": [ - "sha256:2798d26a7b850e759f23f69695a389d676a9c08f2c14f96f0d34d9648c9d5616", - "sha256:4f27c6a690d1bd02b9157e615d8819555b9b359c0c4ec8ff0013d160c31b40bb" + "sha256:49c9149fa9301f98d918f3042f36bed4252d37193d222a1ce8b0e25886442377", + "sha256:c3e65a8ba5e59287308f23679f7fe729b9380531e4f6cdabb2dee99b82834811" ], - "version": "==1.1.0" + "version": "==1.3.0" }, "rq-scheduler": { "hashes": [ @@ -637,18 +651,18 @@ }, "s3transfer": { "hashes": [ - "sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d", - "sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba" + "sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13", + "sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db" ], - "version": "==0.2.1" + "version": "==0.3.3" }, "safety": { "hashes": [ - "sha256:0a3a8a178a9c96242b224f033ee8d1d130c0448b0e6622d12deaf37f6c3b4e59", - "sha256:5059f3ffab3648330548ea9c7403405bbfaf085b11235770825d14c58f24cb78" + "sha256:05f77773bbab834502328b29ed013677aa53ed0c22b6e330aef7d2a7e1dfd838", + "sha256:3016631e0dd17193d6cf12e8ed1af92df399585e8ee0e4b1300d9e7e32b54903" ], "index": "pypi", - "version": "==1.8.5" + "version": "==1.8.7" }, "sentry-sdk": { "hashes": [ @@ -667,24 +681,24 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, - "smmap2": { + "smmap": { "hashes": [ - "sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde", - "sha256:29a9ffa0497e7f2be94ca0ed1ca1aa3cd4cf25a1f6b4f5f87f74b46ed91d609a" + "sha256:171484fe62793e3626c8b05dd752eb2ca01854b0c55a1efc0dc4210fccb65446", + "sha256:5fead614cf2de17ee0707a8c6a5f2aa5a2fc6c698c70993ba42f515485ffda78" ], - "version": "==2.0.5" + "version": "==3.0.1" }, "soupsieve": { "hashes": [ - "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", - "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda" + "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae", + "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69" ], - "version": "==1.9.5" + "version": "==2.0" }, "spectra": { "hashes": [ @@ -695,23 +709,24 @@ }, "spinners": { "hashes": [ - "sha256:f396fea1ee00c0622988d7d2bf2895d26dd6f70850ca2ce94eaca52ca4873560" + "sha256:1eb6aeb4781d72ab42ed8a01dcf20f3002bf50740d7154d12fb8c9769bf9e27f", + "sha256:2fa30d0b72c9650ad12bbe031c9943b8d441e41b4f5602b0ec977a19f3290e98" ], - "version": "==0.0.23" + "version": "==0.0.24" }, "sqlparse": { "hashes": [ - "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", + "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], - "version": "==0.3.0" + "version": "==0.3.1" }, "stevedore": { "hashes": [ - "sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730", - "sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14" + "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b", + "sha256:a4e7dc759fb0f2e3e2f7d8ffe2358c19d45b9b8297f393ef1256858d82f69c9b" ], - "version": "==1.31.0" + "version": "==1.32.0" }, "termcolor": { "hashes": [ @@ -734,12 +749,19 @@ "index": "pypi", "version": "==2.2.2" }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, "uritools": { "hashes": [ - "sha256:522c2027e51e70e0cc40aa703fbf8665a879776826317ff1b68069084030975b", - "sha256:80e8e23cafad54fd85811b5d9ba0fc595d933f5727c61c3937945eec09f99e2b" + "sha256:405917a31ce58a57c8ccd0e4ea290f38baf2f4823819c3688f5331f1aee4ccb0", + "sha256:5ba20a5de979a6a65b3d55998bee86ca1d97075d0f22923b85590b5e39c2b307" ], - "version": "==2.2.0" + "version": "==3.0.0" }, "urlextract": { "hashes": [ @@ -751,18 +773,32 @@ }, "urllib3": { "hashes": [ - "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", - "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + ], + "markers": "python_version != '3.4'", + "version": "==1.25.8" + }, + "virtualenv": { + "hashes": [ + "sha256:4e399f48c6b71228bf79f5febd27e3bbb753d9d5905776a86667bc61ab628a25", + "sha256:9e81279f4a9d16d1c0654a127c2c86e5bca2073585341691882c1e66e31ef8a5" + ], + "version": "==20.0.15" + }, + "virtualenv-clone": { + "hashes": [ + "sha256:07e74418b7cc64f4fda987bf5bc71ebd59af27a7bc9e8a8ee9fd54b1f2390a27", + "sha256:665e48dd54c84b98b71a657acb49104c54e7652bce9c1c4f6c6976ed4c827a29" ], - "markers": "python_version >= '3.4'", - "version": "==1.25.7" + "version": "==0.5.4" }, "watchdog": { "hashes": [ - "sha256:965f658d0732de3188211932aeb0bb457587f04f63ab4c1e33eab878e9de961d" + "sha256:c560efb643faed5ef28784b2245cf8874f939569717a4a12826a173ac644456b" ], "index": "pypi", - "version": "==0.9.0" + "version": "==0.10.2" }, "webpreview": { "hashes": [ @@ -770,6 +806,13 @@ ], "index": "pypi", "version": "==1.6.0" + }, + "zipp": { + "hashes": [ + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" + ], + "version": "==3.1.0" } }, "develop": { diff --git a/requirements.txt b/requirements.txt index e7a50911..8a3379a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,93 +1,100 @@ -i https://pypi.python.org/simple appdirs==1.4.3 -argh==0.26.2 bandit==1.5.1 -beautifulsoup4==4.8.1 -boto3==1.10.29 -botocore==1.13.29 +beautifulsoup4==4.8.2 +boto3==1.12.33 +botocore==1.15.33 certifi==2019.11.28 chardet==3.0.4 -click==7.0 -colorama==0.4.1 +click==7.1.1 +colorama==0.4.3 colormath==3.0.0 -coverage==5.0b1 -croniter==0.3.30 +coverage==5.0.4 +croniter==0.3.31 cursor==1.3.4 -decorator==4.4.1 -django-amazon-ses==2.1.1 -django-appconf==1.0.3 +decorator==4.4.2 +distlib==0.3.0 +django-amazon-ses==3.0.2 +django-appconf==1.0.4 django-cacheops==4.2 django-cursor-pagination==0.1.4 -django-extensions==2.2.5 +django-extensions==2.2.9 django-imagekit==4.0.2 django-media-fixtures==0.0.3 -django-model-utils==3.2.0 +django-model-utils==4.0.0 django-nose==1.4.6 django-ordered-model==3.3.0 django-positions==0.6.0 django-proxy==1.2.1 -django-redis==4.10.0 +django-redis==4.11.0 django-replicated==2.6.1 django-rq-scheduler==1.1.3 -django-rq==2.1.0 -django-storages==1.8 -django==2.2.5 -djangorestframework==3.10.3 +django-rq==2.3.0 +django-storages==1.9.1 +django==2.2.10 +djangorestframework==3.11.0 docutils==0.15.2 -dparse==0.4.1 +dparse==0.5.0 faker==0.9.1 ffmpy==0.2.2 +filelock==3.0.12 funcy==1.14 git+https://github.com/Ian-Foote/rest-framework-generic-relations.git@8a354d1d728dab16532ffb708af3f55a7c0414ec#egg=rest-framework-generic-relations git+https://github.com/lifenautjoe/django-modeltranslation.git@551af9905961038badad9d9a16b2f47996f7685b#egg=django-modeltranslation -gitdb2==2.0.6 -gitpython==3.0.5 -halo==0.0.28 -idna==2.8 -jmespath==0.9.4 -langdetect==1.0.7 +gitdb==4.0.2 +gitpython==3.1.0 +halo==0.0.29 +idna==2.9 +importlib-metadata==1.6.0 ; python_version < '3.8' +jmespath==0.9.5 +langdetect==1.0.8 log-symbols==0.0.14 mixer==6.1.3 mysqlclient==1.4.6 networkx==2.4 nose-exclude==0.5.0 nose==1.3.7 -numpy==1.17.4 +numpy==1.18.2 onesignal-sdk==1.1.0 -packaging==19.2 +packaging==20.3 pathtools==0.1.2 pbr==5.4.4 pilkit==2.0 -pillow==6.2.1 +pillow==7.0.0 pinocchio==0.4.2 +pipenv==2018.11.26 pyjwt==1.7.1 -pyparsing==2.4.5 -python-dateutil==2.8.0 ; python_version >= '2.7' -python-dotenv==0.10.3 +pyparsing==2.4.6 +python-dateutil==2.8.1 +python-dotenv==0.12.0 python-magic==0.4.15 pytz==2019.3 -pyyaml==5.2b1 -redis==3.3.11 +pyyaml==5.3.1 +redis==3.4.1 requests-file==1.4.3 -requests==2.22.0 +requests==2.23.0 rq-scheduler==0.9.1 -rq==1.1.0 -s3transfer==0.2.1 -safety==1.8.5 +rq==1.3.0 +s3transfer==0.3.3 +safety==1.8.7 sentry-sdk==0.10.2 shutilwhich==1.1.0 -six==1.13.0 -smmap2==2.0.5 -soupsieve==1.9.5 +six==1.14.0 +smmap==3.0.1 +soupsieve==2.0 spectra==0.0.11 -spinners==0.0.23 -sqlparse==0.3.0 -stevedore==1.31.0 +spinners==0.0.24 +sqlparse==0.3.1 +stevedore==1.32.0 termcolor==1.1.0 text-unidecode==1.2 tldextract==2.2.2 -uritools==2.2.0 +toml==0.10.0 +uritools==3.0.0 urlextract==0.14.0 -urllib3==1.25.7 ; python_version >= '3.4' -watchdog==0.9.0 +urllib3==1.25.8 ; python_version != '3.4' +virtualenv-clone==0.5.4 +virtualenv==20.0.15 +watchdog==0.10.2 webpreview==1.6.0 +zipp==3.1.0 From 0fd4e2d40149f20d0b8c4ca838e44d76920ccf64 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Fri, 3 Apr 2020 14:11:56 +0200 Subject: [PATCH 28/40] :recycle: refactor mute/unmute post and tests --- openbook_auth/checkers.py | 5 ++ openbook_auth/models.py | 34 ++++++++++++-- openbook_posts/models.py | 1 + openbook_posts/tests/views/test_post.py | 61 +++++++++++++++++++------ 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index 3367a770..0b83356f 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -771,6 +771,11 @@ def check_has_device_with_uuid(user, device_uuid): def check_can_mute_post(user, post): + PostNotificationsSubscription = get_post_notifications_subscription_model() + if not PostNotificationsSubscription.objects.filter(post=post, subscriber=user).exists(): + raise ValidationError( + _('You aren\'t receiving notifications for this post'), + ) if user.has_muted_post_with_id(post_id=post.pk): raise ValidationError( _('Post already muted'), diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 16069bf1..e72d62ca 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -666,7 +666,16 @@ def has_post(self, post): return post.creator_id == self.pk def has_muted_post_with_id(self, post_id): - return self.post_mutes.filter(post_id=post_id).exists() + PostNotificationsSubscription = get_post_notifications_subscription_model() + return PostNotificationsSubscription.objects.filter( + post_id=post_id, + subscriber=self, + comment_notifications=False, + comment_reaction_notifications=False, + reaction_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=False + ).exists() def has_muted_post_comment_with_id(self, post_comment_id): return self.post_comment_mutes.filter(post_comment_id=post_comment_id).exists() @@ -3074,16 +3083,31 @@ def mute_post_with_id(self, post_id): def mute_post(self, post): check_can_mute_post(user=self, post=post) - PostMute = get_post_mute_model() - PostMute.create_post_mute(post_id=post.pk, muter_id=self.pk) + PostNotificationsSubscription = get_post_notifications_subscription_model() + post_notification_subscription = PostNotificationsSubscription.objects.get(post=post, subscriber=self) + post_notification_subscription.update( + comment_notifications=False, + comment_reaction_notifications=False, + reaction_notifications=False, + reply_notifications=False, + reply_where_commented_notifications=False + ) + return post def unmute_post_with_id(self, post_id): Post = get_post_model() post = Post.objects.get(pk=post_id) - check_can_unmute_post(user=self, post=post) - self.post_mutes.filter(post_id=post_id).delete() + PostNotificationsSubscription = get_post_notifications_subscription_model() + post_notification_subscription = PostNotificationsSubscription.objects.get(post=post, subscriber=self) + post_notification_subscription.update( + comment_notifications=True, + comment_reaction_notifications=True, + reaction_notifications=True, + reply_notifications=True, + reply_where_commented_notifications=True + ) return post def mute_post_comment_with_id(self, post_comment_id): diff --git a/openbook_posts/models.py b/openbook_posts/models.py index cda8e704..5df5db5f 100644 --- a/openbook_posts/models.py +++ b/openbook_posts/models.py @@ -1476,3 +1476,4 @@ def update(self, self.reply_where_commented_notifications = reply_where_commented_notifications self.save() + diff --git a/openbook_posts/tests/views/test_post.py b/openbook_posts/tests/views/test_post.py index 04b7cf84..d1870b3c 100644 --- a/openbook_posts/tests/views/test_post.py +++ b/openbook_posts/tests/views/test_post.py @@ -1826,7 +1826,6 @@ def test_can_mute_own_post(self): user = make_user() headers = make_authentication_headers_for_user(user) post = user.create_public_post(text=make_fake_post_text()) - url = self._get_url(post) response = self.client.post(url, **headers) @@ -1850,10 +1849,12 @@ def test_cant_mute_own_post_if_already_muted(self): response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertTrue(user.has_muted_post_with_id(post.pk)) - def test_can_mute_foreign_post_if_public_post(self): + def test_cannot_mute_foreign_post_if_public_post_and_not_subscribed(self): + """ + should not be able to mute post if not receiving notifications and return 400 + """ user = make_user() foreign_user = make_user() @@ -1864,9 +1865,8 @@ def test_can_mute_foreign_post_if_public_post(self): response = self.client.post(url, **headers) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - self.assertTrue(user.has_muted_post_with_id(post.pk)) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertFalse(user.has_muted_post_with_id(post.pk)) def test_cannot_mute_foreign_post_if_encircled_post(self): user = make_user() @@ -1899,12 +1899,17 @@ def test_can_mute_foreign_post_if_part_of_encircled_post(self): foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) + user.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) + url = self._get_url(post) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(user.has_muted_post_with_id(post.pk)) def test_can_mute_community_post_if_public(self): @@ -1915,13 +1920,17 @@ def test_can_mute_community_post_if_public(self): headers = make_authentication_headers_for_user(user) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + user.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) url = self._get_url(post) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(user.has_muted_post_with_id(post.pk)) def test_cannot_mute_closed_community_post(self): @@ -1980,6 +1989,11 @@ def test_can_mute_closed_community_post_administrator(self): headers = make_authentication_headers_for_user(admin) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + admin.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) post.is_closed = True post.save() @@ -2006,6 +2020,11 @@ def test_can_mute_closed_community_post_if_moderator(self): headers = make_authentication_headers_for_user(moderator) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + moderator.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) post.is_closed = True post.save() @@ -2016,7 +2035,7 @@ def test_can_mute_closed_community_post_if_moderator(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(moderator.has_muted_post_with_id(post.pk)) - def test_cant_mute_community_post_if_private_and_not_member(self): + def test_cannot_mute_community_post_if_private_and_not_member(self): user = make_user() foreign_user = make_user() @@ -2030,7 +2049,6 @@ def test_cant_mute_community_post_if_private_and_not_member(self): response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertFalse(user.has_muted_post_with_id(post.pk)) def test_can_mute_community_post_if_private_and_member(self): @@ -2046,13 +2064,17 @@ def test_can_mute_community_post_if_private_and_member(self): community_name=community.name) user.join_community_with_name(community_name=community.name) + user.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) url = self._get_url(post) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(user.has_muted_post_with_id(post.pk)) def _get_url(self, post): @@ -2085,7 +2107,6 @@ def test_can_unmute_own_post(self): response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(user.has_muted_post_with_id(post.pk)) def test_cant_unmute_own_post_if_already_unmuted(self): @@ -2116,12 +2137,16 @@ def test_cannot_unmute_closed_community_post(self): headers = make_authentication_headers_for_user(user) post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + user.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) user.mute_post_with_id(post.pk) post.is_closed = True post.save() url = self._get_url(post) - response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -2162,6 +2187,11 @@ def test_can_unmute_closed_community_post_administrator(self): headers = make_authentication_headers_for_user(admin) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + admin.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) admin.mute_post_with_id(post.pk) post.is_closed = True post.save() @@ -2189,6 +2219,11 @@ def test_can_unmute_closed_community_post_if_moderator(self): headers = make_authentication_headers_for_user(moderator) post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + moderator.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_where_commented_notifications=True + ) moderator.mute_post_with_id(post.pk) post.is_closed = True post.save() From a60258398bc7b0be2f4e6093ca520c2d37ade6e3 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Fri, 3 Apr 2020 15:20:41 +0200 Subject: [PATCH 29/40] :recycle: refactor mute unmute post comment and tests --- openbook_auth/checkers.py | 8 ++- openbook_auth/models.py | 26 ++++++-- .../tests/views/test_post_comment.py | 64 +++++++++++++++---- 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/openbook_auth/checkers.py b/openbook_auth/checkers.py index 0b83356f..734b77c3 100644 --- a/openbook_auth/checkers.py +++ b/openbook_auth/checkers.py @@ -6,7 +6,7 @@ from openbook_common.utils.model_loaders import get_post_model, get_community_model, get_post_comment_model, \ get_language_model, get_user_model, get_emoji_group_model, get_post_reaction_model, get_user_invite_model, \ get_community_notifications_subscription_model, get_user_notifications_subscription_model, \ - get_post_notifications_subscription_model + get_post_notifications_subscription_model, get_post_comment_notifications_subscription_model from openbook_common import checkers as common_checkers @@ -796,6 +796,12 @@ def check_has_muted_post_with_id(user, post_id): def check_can_mute_post_comment(user, post_comment): + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + if not PostCommentNotificationsSubscription.objects.filter(post_comment=post_comment, subscriber=user).exists(): + raise ValidationError( + _('You aren\'t receiving notifications for this post comment'), + ) + if user.has_muted_post_comment_with_id(post_comment_id=post_comment.pk): raise ValidationError( _('Post comment already muted'), diff --git a/openbook_auth/models.py b/openbook_auth/models.py index e72d62ca..afb0e26c 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -678,7 +678,13 @@ def has_muted_post_with_id(self, post_id): ).exists() def has_muted_post_comment_with_id(self, post_comment_id): - return self.post_comment_mutes.filter(post_comment_id=post_comment_id).exists() + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + return PostCommentNotificationsSubscription.objects.filter( + post_comment_id=post_comment_id, + subscriber=self, + reaction_notifications=False, + reply_notifications=False + ).exists() def has_disabled_comment_notifications_for_post(self, post_id): return self.post_notifications_subscriptions.filter(post_id=post_id, comment_notifications=False).exists() @@ -3117,16 +3123,26 @@ def mute_post_comment_with_id(self, post_comment_id): def mute_post_comment(self, post_comment): check_can_mute_post_comment(user=self, post_comment=post_comment) - PostCommentMute = get_post_comment_mute_model() - PostCommentMute.create_post_comment_mute(post_comment_id=post_comment.pk, muter_id=self.pk) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + post_comment_notification_subscription = PostCommentNotificationsSubscription.objects.get( + post_comment=post_comment, subscriber=self) + post_comment_notification_subscription.update( + reaction_notifications=False, + reply_notifications=False + ) return post_comment def unmute_post_comment_with_id(self, post_comment_id): Post_comment = get_post_comment_model() post_comment = Post_comment.objects.get(pk=post_comment_id) - check_can_unmute_post_comment(user=self, post_comment=post_comment) - self.post_comment_mutes.filter(post_comment_id=post_comment_id).delete() + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + post_comment_notification_subscription = PostCommentNotificationsSubscription.objects.get( + post_comment=post_comment, subscriber=self) + post_comment_notification_subscription.update( + reaction_notifications=True, + reply_notifications=True + ) return post_comment def translate_post_comment_with_id(self, post_comment_id): diff --git a/openbook_posts/tests/views/test_post_comment.py b/openbook_posts/tests/views/test_post_comment.py index da433e01..2e1ea964 100644 --- a/openbook_posts/tests/views/test_post_comment.py +++ b/openbook_posts/tests/views/test_post_comment.py @@ -2215,13 +2215,16 @@ def test_can_mute_foreign_post_comment_if_public_post_comment(self): headers = make_authentication_headers_for_user(user) post = foreign_user.create_public_post(text=make_fake_post_text()) post_comment = foreign_user.comment_post(post=post, text=make_fake_post_comment_text()) - + user.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) url = self._get_url(post=post, post_comment=post_comment) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(user.has_muted_post_comment_with_id(post_comment.pk)) def test_cannot_mute_foreign_post_comment_if_encircled_post(self): @@ -2235,13 +2238,11 @@ def test_cannot_mute_foreign_post_comment_if_encircled_post(self): post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) post_comment = foreign_user.comment_post(post=post, text=make_fake_post_comment_text()) - url = self._get_url(post=post, post_comment=post_comment) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertFalse(user.has_muted_post_comment_with_id(post_comment.pk)) def test_can_mute_foreign_post_comment_if_part_of_encircled_post_comment(self): @@ -2258,13 +2259,17 @@ def test_can_mute_foreign_post_comment_if_part_of_encircled_post_comment(self): foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) + user.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) url = self._get_url(post=post, post_comment=post_comment) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(user.has_muted_post_comment_with_id(post_comment.pk)) def test_can_mute_community_post_comment_if_public(self): @@ -2277,7 +2282,11 @@ def test_can_mute_community_post_comment_if_public(self): post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = foreign_user.comment_post(post=post, text=make_fake_post_comment_text()) - + user.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) url = self._get_url(post=post, post_comment=post_comment) response = self.client.post(url, **headers) @@ -2300,7 +2309,11 @@ def test_can_mute_closed_community_post_comment(self): post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = foreign_user.comment_post(post=post, text=make_fake_post_comment_text()) - + user.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) post_comment.is_closed = True post_comment.save() @@ -2350,7 +2363,11 @@ def test_can_mute_closed_community_post_comment_administrator(self): post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = user.comment_post(post=post, text=make_fake_post_comment_text()) - + admin.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) post_comment.is_closed = True post_comment.save() @@ -2379,7 +2396,11 @@ def test_can_mute_closed_community_post_comment_if_moderator(self): post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = user.comment_post(post=post, text=make_fake_post_comment_text()) - + moderator.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) post_comment.is_closed = True post_comment.save() @@ -2390,7 +2411,7 @@ def test_can_mute_closed_community_post_comment_if_moderator(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(moderator.has_muted_post_comment_with_id(post_comment.pk)) - def test_cant_mute_community_post_comment_if_private_and_not_member(self): + def test_cannot_mute_community_post_comment_if_private_and_not_member(self): user = make_user() foreign_user = make_user() @@ -2400,13 +2421,11 @@ def test_cant_mute_community_post_comment_if_private_and_not_member(self): post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = foreign_user.comment_post(post=post, text=make_fake_post_comment_text()) - url = self._get_url(post=post, post_comment=post_comment) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertFalse(user.has_muted_post_comment_with_id(post_comment.pk)) def test_can_mute_community_post_comment_if_private_and_member(self): @@ -2424,13 +2443,17 @@ def test_can_mute_community_post_comment_if_private_and_member(self): community_name=community.name) user.join_community_with_name(community_name=community.name) + user.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) url = self._get_url(post=post, post_comment=post_comment) response = self.client.post(url, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(user.has_muted_post_comment_with_id(post_comment.pk)) def _get_url(self, post, post_comment): @@ -2499,6 +2522,11 @@ def test_can_unmute_closed_community_post_comment(self): post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = foreign_user.comment_post(post=post, text=make_fake_post_comment_text()) + user.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) user.mute_post_comment_with_id(post_comment.pk) post_comment.is_closed = True post_comment.save() @@ -2549,6 +2577,11 @@ def test_can_unmute_closed_community_post_comment_administrator(self): post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = user.comment_post(post=post, text=make_fake_post_comment_text()) + admin.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) admin.mute_post_comment_with_id(post_comment.pk) post_comment.is_closed = True post_comment.save() @@ -2578,6 +2611,11 @@ def test_can_unmute_closed_community_post_comment_if_moderator(self): post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) post_comment = user.comment_post(post=post, text=make_fake_post_comment_text()) + moderator.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment.id, + reply_notifications=True, + reaction_notifications=True + ) moderator.mute_post_comment_with_id(post_comment.pk) post_comment.is_closed = True post_comment.save() From 9266a546dacb1e6d2f82a252be39005fc39ce32f Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 14 Apr 2020 18:53:35 +0200 Subject: [PATCH 30/40] :fire: remove fields from notification subscription model --- .../migrations/0072_auto_20200414_1852.py | 21 ++++++ openbook_posts/models.py | 72 +++++++++++-------- 2 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 openbook_posts/migrations/0072_auto_20200414_1852.py diff --git a/openbook_posts/migrations/0072_auto_20200414_1852.py b/openbook_posts/migrations/0072_auto_20200414_1852.py new file mode 100644 index 00000000..b841e31f --- /dev/null +++ b/openbook_posts/migrations/0072_auto_20200414_1852.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.10 on 2020-04-14 16:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_posts', '0071_auto_20200313_1648'), + ] + + operations = [ + migrations.RemoveField( + model_name='postnotificationssubscription', + name='comment_reaction_notifications', + ), + migrations.RemoveField( + model_name='postnotificationssubscription', + name='reply_where_commented_notifications', + ), + ] diff --git a/openbook_posts/models.py b/openbook_posts/models.py index 5df5db5f..d0c08bc1 100644 --- a/openbook_posts/models.py +++ b/openbook_posts/models.py @@ -243,21 +243,53 @@ def _get_trending_posts_old_query(cls): return trending_posts_query @classmethod - def get_post_comment_notification_target_users(cls, post): + def get_post_comment_notification_target_users(cls, post, post_commenter): """ Returns the users that qualify to be notified of a post comment. This includes the post creator and other post commenters and post subscribers :return: """ + # post subscribers + post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk, + post_notifications_subscriptions__comment_notifications=True) + + # Add other post commenters, exclude replies to comments + other_commenters_query = Q(posts_comments__post_id=post.pk, posts_comments__parent_comment_id=None,) + + other_target_users = User.objects. \ + only('id', 'username', 'notifications_settings__post_comment_notifications'). \ + filter((post_subscribers_query | other_commenters_query) & ~Q(id=post_commenter.pk)) + + post_creator = User.objects. \ + only('id', 'username', 'notifications_settings__post_comment_notifications'). \ + filter(pk=post.creator_id) + + return other_target_users.union(post_creator) + + @classmethod + def get_post_comment_reply_notification_target_users(cls, post, post_comment): + """ + Returns the users that qualify to be notified of a post comment reply. + This includes the post comment creator and other post commenters and post subscribers + :return: + """ # post subscribers - post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk) + post_subscribers_query = Q(post_notifications_subscriptions__post_id=post.pk, + post_notifications_subscriptions__reply_notifications=True) + + # post comment subscribers + post_comment_reply_subscribers_query = Q(post_comment_notifications_subscriptions__post_comment_id=post_comment.pk) - target_users = User.objects.\ + post_target_users = User.objects.\ only('id', 'username', 'notifications_settings__post_comment_notifications').\ filter(post_subscribers_query) - return target_users + post_comment_reply_target_users = User.objects.\ + only('id', 'username', 'notifications_settings__post_comment_notifications').\ + filter(post_comment_reply_subscribers_query) + + return post_target_users.union(post_comment_reply_target_users) @classmethod def get_community_notification_target_subscriptions(cls, post): @@ -777,10 +809,8 @@ def _subscribe_creator_to_post_notifications(self): post=self, subscriber=self.creator, comment_notifications=True, - comment_reaction_notifications=True, reaction_notifications=True, - reply_notifications=True, - reply_where_commented_notifications=True + reply_notifications=False, ) @@ -1382,10 +1412,8 @@ class PostNotificationsSubscription(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='notifications_subscriptions', null=False, blank=False) reaction_notifications = models.BooleanField(default=True, blank=False) - comment_reaction_notifications = models.BooleanField(default=True, blank=False) comment_notifications = models.BooleanField(default=True, blank=False) reply_notifications = models.BooleanField(default=False, blank=False) - reply_where_commented_notifications = models.BooleanField(default=True, blank=False) class Meta: unique_together = ('post', 'subscriber',) @@ -1393,19 +1421,15 @@ class Meta: @classmethod def create_post_notifications_subscription(cls, subscriber, post, comment_notifications=False, - comment_reaction_notifications=False, reaction_notifications=False, - reply_notifications=False, - reply_where_commented_notifications=False): + reply_notifications=False): if not cls.objects.filter(subscriber=subscriber, post=post).exists(): return cls.objects.create(subscriber=subscriber, post=post, comment_notifications=comment_notifications, - comment_reaction_notifications=comment_reaction_notifications, reaction_notifications=reaction_notifications, - reply_notifications=reply_notifications, - reply_where_commented_notifications=reply_where_commented_notifications) + reply_notifications=reply_notifications) post_notifications_subscription = cls.objects.get(subscriber=subscriber, post=post) return post_notifications_subscription @@ -1413,10 +1437,8 @@ def create_post_notifications_subscription(cls, subscriber, post, @classmethod def get_or_create_post_notifications_subscription(cls, subscriber, post, comment_notifications=False, - comment_reaction_notifications=False, reaction_notifications=False, - reply_notifications=False, - reply_where_commented_notifications=False): + reply_notifications=False): try: post_notifications_subscription = cls.objects.get(subscriber_id=subscriber.pk, post_id=post.pk) @@ -1425,10 +1447,8 @@ def get_or_create_post_notifications_subscription(cls, subscriber, post, subscriber=subscriber, post=post, comment_notifications=comment_notifications, - comment_reaction_notifications=comment_reaction_notifications, reaction_notifications=reaction_notifications, - reply_notifications=reply_notifications, - reply_where_commented_notifications=reply_where_commented_notifications + reply_notifications=reply_notifications ) return post_notifications_subscription @@ -1455,25 +1475,17 @@ def are_comment_notifications_enabled_for_user_with_username_and_post_with_id(cl def update(self, comment_notifications=None, - comment_reaction_notifications=None, reaction_notifications=None, - reply_notifications=None, - reply_where_commented_notifications=None): + reply_notifications=None): if comment_notifications is not None: self.comment_notifications = comment_notifications - if comment_reaction_notifications is not None: - self.comment_reaction_notifications = comment_reaction_notifications - if reaction_notifications is not None: self.reaction_notifications = reaction_notifications if reply_notifications is not None: self.reply_notifications = reply_notifications - if reply_where_commented_notifications is not None: - self.reply_where_commented_notifications = reply_where_commented_notifications - self.save() From f3fcdf946d0a2cf97f90d8625612b2da0b4c54bf Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 14 Apr 2020 18:55:49 +0200 Subject: [PATCH 31/40] :recycle: refactor views,methods n tests --- openbook/urls.py | 4 +- openbook_auth/models.py | 125 ++++-------------- openbook_posts/tests/views/test_post.py | 8 -- .../tests/views/test_post_comment_replies.py | 82 +++--------- openbook_posts/views/post/serializers.py | 12 +- openbook_posts/views/post/views.py | 25 ++-- .../views/post_comment/serializers.py | 40 +++++- openbook_posts/views/post_comment/views.py | 62 ++++++++- openbook_posts/views/posts/serializers.py | 4 +- 9 files changed, 164 insertions(+), 198 deletions(-) diff --git a/openbook/urls.py b/openbook/urls.py index 4d050e21..45f70ff8 100644 --- a/openbook/urls.py +++ b/openbook/urls.py @@ -79,7 +79,7 @@ PostCommentReactionsEmojiCount from openbook_posts.views.post_comment.post_comment_replies.views import PostCommentReplies from openbook_posts.views.post_comment.views import PostCommentItem, MutePostComment, UnmutePostComment, \ - TranslatePostComment + TranslatePostComment, PostCommentNotificationsSubscriptionSettings from openbook_posts.views.post_comments.views import PostComments, PostCommentsDisable, PostCommentsEnable from openbook_posts.views.post_media.views import PostMedia from openbook_posts.views.post_reaction.views import PostReactionItem @@ -172,6 +172,8 @@ post_comment_notifications_patterns = [ path('mute/', MutePostComment.as_view(), name='mute-post-comment'), path('unmute/', UnmutePostComment.as_view(), name='unmute-post-comment'), + path('subscribe/', PostCommentNotificationsSubscriptionSettings.as_view(), + name='post-comment-notifications-subscription-settings'), ] post_comment_patterns = [ diff --git a/openbook_auth/models.py b/openbook_auth/models.py index afb0e26c..6f886d63 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -666,25 +666,10 @@ def has_post(self, post): return post.creator_id == self.pk def has_muted_post_with_id(self, post_id): - PostNotificationsSubscription = get_post_notifications_subscription_model() - return PostNotificationsSubscription.objects.filter( - post_id=post_id, - subscriber=self, - comment_notifications=False, - comment_reaction_notifications=False, - reaction_notifications=False, - reply_notifications=False, - reply_where_commented_notifications=False - ).exists() + return self.post_mutes.filter(post_id=post_id).exists() def has_muted_post_comment_with_id(self, post_comment_id): - PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() - return PostCommentNotificationsSubscription.objects.filter( - post_comment_id=post_comment_id, - subscriber=self, - reaction_notifications=False, - reply_notifications=False - ).exists() + return self.post_comment_mutes.filter(post_comment_id=post_comment_id).exists() def has_disabled_comment_notifications_for_post(self, post_id): return self.post_notifications_subscriptions.filter(post_id=post_id, comment_notifications=False).exists() @@ -692,14 +677,8 @@ def has_disabled_comment_notifications_for_post(self, post_id): def has_disabled_reaction_notifications_for_post_with_id(self, post_id): return self.post_notifications_subscriptions.filter(post_id=post_id, reaction_notifications=False).exists() - def has_disabled_reply_notifications_for_post(self, post_id, post_comment_id): - has_replied = self.has_replied_on_comment_with_id(post_comment_id=post_comment_id) - is_commenter = self.posts_comments.filter(id=post_comment_id).exists() - if has_replied or is_commenter: - return self.post_notifications_subscriptions.filter(post_id=post_id, - reply_where_commented_notifications=False).exists() - else: - return self.post_notifications_subscriptions.filter(post_id=post_id, reply_notifications=False).exists() + def has_disabled_reply_notifications_for_post(self, post_id): + return self.post_notifications_subscriptions.filter(post_id=post_id, reply_notifications=False).exists() def has_disabled_reply_notifications_for_post_comment(self, post_comment_id): return self.post_comment_notifications_subscriptions.filter(post_comment_id=post_comment_id, @@ -834,29 +813,25 @@ def has_post_mention_notifications_enabled(self): return self.notifications_settings.post_user_mention_notifications def has_reaction_notifications_enabled_for_post_with_id(self, post_id): - # @TODO :remove legacy has_muted_post_with_id check later - return self.notifications_settings.post_reaction_notifications and \ not self.has_muted_post_with_id(post_id=post_id) and \ not self.has_disabled_reaction_notifications_for_post_with_id(post_id=post_id) def has_reaction_notifications_enabled_for_post_comment(self, post_comment): - # @TODO :remove legacy has_muted_* checks later return self.notifications_settings.post_comment_reaction_notifications and \ not self.has_muted_post_with_id(post_id=post_comment.post_id) and \ not self.has_muted_post_comment_with_id(post_comment_id=post_comment.id) and \ - not self.has_disabled_reaction_notifications_for_post_with_id(post_id=post_comment.post.pk) and \ not self.has_disabled_reaction_notifications_for_post_comment(post_comment=post_comment) def has_comment_notifications_enabled_for_post_with_id(self, post_id): return self.notifications_settings.post_comment_notifications and \ + not self.has_muted_post_with_id(post_id=post_id) and \ not self.has_disabled_comment_notifications_for_post(post_id=post_id) def has_reply_notifications_enabled_for_post_comment(self, post_comment): return self.notifications_settings.post_comment_reply_notifications and \ - not self.has_disabled_reply_notifications_for_post( - post_id=post_comment.post_id, - post_comment_id=post_comment.id) and \ + not self.has_muted_post_with_id(post_id=post_comment.post_id) and \ + not self.has_muted_post_comment_with_id(post_comment_id=post_comment.id) and \ not self.has_disabled_reply_notifications_for_post_comment(post_comment_id=post_comment.id) def has_connection_request_notifications_enabled(self): @@ -1098,11 +1073,10 @@ def react_to_post(self, post, emoji_id): else: post_reaction = post.react(reactor=self, emoji_id=emoji_id) if post_reaction.post.creator_id != self.pk: - if post.creator.has_reaction_notifications_enabled_for_post_with_id(post_id=post.pk) and \ - not post.creator.has_blocked_user_with_id(self.pk): + if not post.creator.has_blocked_user_with_id(self.pk): self._create_post_reaction_notification(post_reaction=post_reaction) - self._send_post_reaction_push_notification(post_reaction=post_reaction) - + if post.creator.has_reaction_notifications_enabled_for_post_with_id(post_id=post.pk): + self._send_post_reaction_push_notification(post_reaction=post_reaction) return post_reaction def delete_reaction_with_id_for_post_with_id(self, post_reaction_id, post_id): @@ -1134,10 +1108,10 @@ def react_to_post_comment(self, post_comment, emoji_id): commenter_has_reaction_notifications_enabled = \ post_comment.commenter.has_reaction_notifications_enabled_for_post_comment(post_comment=post_comment) - if commenter_has_reaction_notifications_enabled and \ - not post_comment.commenter.has_blocked_user_with_id(self.pk): - self._send_post_comment_reaction_push_notification(post_comment_reaction=post_comment_reaction) + if not post_comment.commenter.has_blocked_user_with_id(self.pk): self._create_post_comment_reaction_notification(post_comment_reaction=post_comment_reaction) + if commenter_has_reaction_notifications_enabled: + self._send_post_comment_reaction_push_notification(post_comment_reaction=post_comment_reaction) return post_comment_reaction @@ -1239,10 +1213,8 @@ def comment_post(self, post, text): post=post, subscriber=self, comment_notifications=True, - comment_reaction_notifications=True, reaction_notifications=False, reply_notifications=False, - reply_where_commented_notifications=True ) # create post comment notifications subscription for user PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() @@ -1254,7 +1226,7 @@ def comment_post(self, post, text): ) # Language should also be prefetched here, for some reason it doesnt work.... - post_notification_target_users = Post.get_post_comment_notification_target_users(post=post) + post_notification_target_users = Post.get_post_comment_notification_target_users(post=post, post_commenter=self) PostCommentNotification = get_post_comment_notification_model() @@ -1289,10 +1261,8 @@ def reply_to_comment_for_post(self, post_comment, post, text): post=post, subscriber=self, comment_notifications=False, - comment_reaction_notifications=True, reaction_notifications=False, reply_notifications=False, - reply_where_commented_notifications=True ) # create post comment notifications subscription for user PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() @@ -1313,7 +1283,7 @@ def reply_to_comment_for_post(self, post_comment, post, text): Post = get_post_model() # Language should also be prefetched here, for some reason it doesnt work.... - post_notification_target_users = Post.get_post_comment_notification_target_users(post=post) + post_notification_target_users = Post.get_post_comment_reply_notification_target_users(post=post, post_comment=post_comment) PostCommentReplyNotification = get_post_comment_reply_notification_model() @@ -2243,8 +2213,8 @@ def create_post_comment_notifications_subscription_for_comment_with_id(self, pos return post_comment_notifications_subscription def update_post_comment_notifications_subscription_for_comment_with_id(self, post_comment_id, - reply_notifications=None, - reaction_notifications=None): + reply_notifications=None, + reaction_notifications=None): PostComment = get_post_comment_model() post_comment = PostComment.objects.get(id=post_comment_id) @@ -2264,10 +2234,8 @@ def update_post_comment_notifications_subscription_for_comment_with_id(self, pos def create_post_notifications_subscription_for_post_with_id(self, post_id, comment_notifications=False, - comment_reaction_notifications=False, reaction_notifications=False, - reply_notifications=False, - reply_where_commented_notifications=False): + reply_notifications=False): Post = get_post_model() post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() @@ -2277,28 +2245,21 @@ def create_post_notifications_subscription_for_post_with_id(self, post_id, if reaction_notifications is True: check_can_update_reaction_notifications_for_post(user=self, post=post) - if comment_reaction_notifications is True: - check_can_update_comment_reaction_notifications_for_post(user=self, post=post) - post_notifications_subscription = PostNotificationsSubscription.\ create_post_notifications_subscription( post=post, subscriber=self, comment_notifications=comment_notifications, - comment_reaction_notifications=comment_reaction_notifications, reaction_notifications=reaction_notifications, - reply_notifications=reply_notifications, - reply_where_commented_notifications=reply_where_commented_notifications + reply_notifications=reply_notifications ) return post_notifications_subscription def update_post_notifications_subscription_for_post_with_id(self, post_id, comment_notifications=None, - comment_reaction_notifications=None, reaction_notifications=None, - reply_notifications=None, - reply_where_commented_notifications=None): + reply_notifications=None): Post = get_post_model() post = Post.objects.get(id=post_id) PostNotificationsSubscription = get_post_notifications_subscription_model() @@ -2311,15 +2272,10 @@ def update_post_notifications_subscription_for_post_with_id(self, post_id, if reaction_notifications is not None: check_can_update_reaction_notifications_for_post(user=self, post=post) - if comment_reaction_notifications is not None: - check_can_update_comment_reaction_notifications_for_post(user=self, post=post) - post_notifications_subscription.update( comment_notifications=comment_notifications, - comment_reaction_notifications=comment_reaction_notifications, reaction_notifications=reaction_notifications, reply_notifications=reply_notifications, - reply_where_commented_notifications=reply_where_commented_notifications ) return post_notifications_subscription @@ -3089,31 +3045,16 @@ def mute_post_with_id(self, post_id): def mute_post(self, post): check_can_mute_post(user=self, post=post) - PostNotificationsSubscription = get_post_notifications_subscription_model() - post_notification_subscription = PostNotificationsSubscription.objects.get(post=post, subscriber=self) - post_notification_subscription.update( - comment_notifications=False, - comment_reaction_notifications=False, - reaction_notifications=False, - reply_notifications=False, - reply_where_commented_notifications=False - ) - + PostMute = get_post_mute_model() + PostMute.create_post_mute(post_id=post.pk, muter_id=self.pk) return post def unmute_post_with_id(self, post_id): Post = get_post_model() post = Post.objects.get(pk=post_id) + check_can_unmute_post(user=self, post=post) - PostNotificationsSubscription = get_post_notifications_subscription_model() - post_notification_subscription = PostNotificationsSubscription.objects.get(post=post, subscriber=self) - post_notification_subscription.update( - comment_notifications=True, - comment_reaction_notifications=True, - reaction_notifications=True, - reply_notifications=True, - reply_where_commented_notifications=True - ) + self.post_mutes.filter(post_id=post_id).delete() return post def mute_post_comment_with_id(self, post_comment_id): @@ -3123,26 +3064,16 @@ def mute_post_comment_with_id(self, post_comment_id): def mute_post_comment(self, post_comment): check_can_mute_post_comment(user=self, post_comment=post_comment) - PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() - post_comment_notification_subscription = PostCommentNotificationsSubscription.objects.get( - post_comment=post_comment, subscriber=self) - post_comment_notification_subscription.update( - reaction_notifications=False, - reply_notifications=False - ) + PostCommentMute = get_post_comment_mute_model() + PostCommentMute.create_post_comment_mute(post_comment_id=post_comment.pk, muter_id=self.pk) return post_comment def unmute_post_comment_with_id(self, post_comment_id): Post_comment = get_post_comment_model() post_comment = Post_comment.objects.get(pk=post_comment_id) + check_can_unmute_post_comment(user=self, post_comment=post_comment) - PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() - post_comment_notification_subscription = PostCommentNotificationsSubscription.objects.get( - post_comment=post_comment, subscriber=self) - post_comment_notification_subscription.update( - reaction_notifications=True, - reply_notifications=True - ) + self.post_comment_mutes.filter(post_comment_id=post_comment_id).delete() return post_comment def translate_post_comment_with_id(self, post_comment_id): diff --git a/openbook_posts/tests/views/test_post.py b/openbook_posts/tests/views/test_post.py index d1870b3c..822fd673 100644 --- a/openbook_posts/tests/views/test_post.py +++ b/openbook_posts/tests/views/test_post.py @@ -1902,7 +1902,6 @@ def test_can_mute_foreign_post_if_part_of_encircled_post(self): user.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) url = self._get_url(post) @@ -1923,7 +1922,6 @@ def test_can_mute_community_post_if_public(self): user.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) url = self._get_url(post) @@ -1992,7 +1990,6 @@ def test_can_mute_closed_community_post_administrator(self): admin.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) post.is_closed = True post.save() @@ -2023,7 +2020,6 @@ def test_can_mute_closed_community_post_if_moderator(self): moderator.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) post.is_closed = True post.save() @@ -2067,7 +2063,6 @@ def test_can_mute_community_post_if_private_and_member(self): user.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) url = self._get_url(post) @@ -2140,7 +2135,6 @@ def test_cannot_unmute_closed_community_post(self): user.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) user.mute_post_with_id(post.pk) post.is_closed = True @@ -2190,7 +2184,6 @@ def test_can_unmute_closed_community_post_administrator(self): admin.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) admin.mute_post_with_id(post.pk) post.is_closed = True @@ -2222,7 +2215,6 @@ def test_can_unmute_closed_community_post_if_moderator(self): moderator.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=True, - reply_where_commented_notifications=True ) moderator.mute_post_with_id(post.pk) post.is_closed = True diff --git a/openbook_posts/tests/views/test_post_comment_replies.py b/openbook_posts/tests/views/test_post_comment_replies.py index 9c12b5d6..7520f58c 100644 --- a/openbook_posts/tests/views/test_post_comment_replies.py +++ b/openbook_posts/tests/views/test_post_comment_replies.py @@ -1389,7 +1389,6 @@ def test_commenting_reply_in_commented_post_by_foreign_user_creates_foreign_noti foreign_user.update_post_notifications_subscription_for_post_with_id( post_id=post.pk, comment_notifications=False, - reply_where_commented_notifications=False, reply_notifications=False) reply_comment_text = make_fake_post_comment_text() @@ -1524,7 +1523,6 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(s foreign_user.update_post_notifications_subscription_for_post_with_id( post_id=post.pk, comment_notifications=False, - reply_where_commented_notifications=False, reply_notifications=False) reply_comment_text = make_fake_post_comment_text() @@ -1549,46 +1547,6 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(s send_post_comment_reply_push_notification_call.assert_has_calls(calls, any_order=True) - @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') - def test_replying_on_foreign_post_comment_sends_push_notification_to_post_creator(self, - send_post_comment_reply_push_notification_call): - """ - should send a push notification to the post creator when replying on a foreign post comment - """ - user = make_user() - headers = make_authentication_headers_for_user(user) - - post_creator = make_user() - foreign_user = make_user() - - post = post_creator.create_public_post(text=make_fake_post_text()) - post_comment = foreign_user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - - reply_comment_text = make_fake_post_comment_text() - - data = self._get_create_post_comment_request_data(reply_comment_text) - - send_post_comment_reply_push_notification_call.reset_mock() - - url = self._get_url(post, post_comment) - self.client.put(url, data, **headers) - - post_comment_reply = PostComment.objects.get( - commenter_id=user.pk, - parent_comment_id=post_comment.id) - - calls = [ - call( - post_comment_reply=post_comment_reply, - target_user=post_creator - ), - call( - post_comment_reply=post_comment_reply, - target_user=foreign_user - ) - ] - - send_post_comment_reply_push_notification_call.assert_has_calls(calls, any_order=True) @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') def test_replying_on_foreign_post_comment_sends_push_notification_to_other_replier(self, @@ -1613,7 +1571,6 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_other_repli post_creator.update_post_notifications_subscription_for_post_with_id( post_id=post.pk, comment_notifications=False, - reply_where_commented_notifications=False, reply_notifications=False) reply_comment_text = make_fake_post_comment_text() @@ -1691,23 +1648,13 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_pos post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) # mute post creator notifications - post_creator.update_post_notifications_subscription_for_post_with_id( - post_id=post.pk, - comment_notifications=False, - reply_notifications=False, - reply_where_commented_notifications=False - ) + post_creator.mute_post_with_id(post_id=post.pk) foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - foreign_user.update_post_notifications_subscription_for_post_with_id( - post_id=post.pk, - comment_notifications=False, - reply_notifications=False, - reply_where_commented_notifications=False - ) + foreign_user.mute_post_with_id(post_id=post.pk) send_post_comment_reply_push_notification_call.reset_mock() @@ -2185,8 +2132,7 @@ def test_foreign_user_replying_on_post_comment_doesnt_send_push_notification_to_ post_subscriber.create_post_notifications_subscription_for_post_with_id(post_id=post.id) post_subscriber.update_post_notifications_subscription_for_post_with_id( post_id=post.id, - reply_notifications=False, - reply_where_commented_notifications=False) + reply_notifications=False) send_post_comment_reply_push_notification_call.reset_mock() @@ -2237,10 +2183,10 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_to_subsc send_post_comment_reply_push_notification_call.assert_not_called() @mock.patch('openbook_notifications.helpers.send_post_comment_reply_push_notification') - def test_replying_on_foreign_post_comment_does_not_send_subscription_notification_to_a_subscribed_commenter(self, + def test_replying_on_foreign_post_comment_does_send_subscription_notification_to_a_subscribed_commenter(self, send_post_comment_reply_push_notification_call): """ - should NOT send post subscription push notification to the commenter when replying on a foreign post comment + should send post subscription push notification to the commenter when replying on a foreign post comment """ user = make_user() headers = make_authentication_headers_for_user(user) @@ -2254,8 +2200,10 @@ def test_replying_on_foreign_post_comment_does_not_send_subscription_notificatio foreign_post_comment = foreign_user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - # subscribe to post comment notifications - commenter.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + # update post comment reply notifications + commenter.update_post_notifications_subscription_for_post_with_id( + post_id=post.id, + reply_notifications=True) reply_comment_text = make_fake_post_comment_text() @@ -2275,10 +2223,10 @@ def test_replying_on_foreign_post_comment_does_not_send_subscription_notificatio ), call( post_comment_reply=post_comment_reply, - target_user=post_creator + target_user=commenter ) ] - # only called for foreign user and creator + # only called for foreign user and commenter send_post_comment_reply_push_notification_call.assert_has_calls(calls, any_order=True) def test_replying_on_post_comment_doesnt_create_post_subscription_push_notification_when_user_blocked(self): @@ -2334,10 +2282,8 @@ def test_replying_on_post_comment_doesnt_send_post_subscription_push_notificatio blocking_user_aka_subscriber.create_post_notifications_subscription_for_post_with_id( post_id=post.id, comment_notifications=False, - comment_reaction_notifications=False, reaction_notifications=False, reply_notifications=True, - reply_where_commented_notifications=True ) send_post_comment_reply_push_notification_call.reset_mock() @@ -2403,7 +2349,11 @@ def test_reply_in_community_post_by_admin_does_create_subscription_notification_ post_comment = post_creator.comment_post(post=post, text=make_fake_post_text()) # subscribe to notifications - admin.create_post_notifications_subscription_for_post_with_id(post_id=post.id) + admin.create_post_notifications_subscription_for_post_with_id( + post_id=post.id, + comment_notifications=True, + reply_notifications=True) + # post will be closed now post.is_closed = True post.save() diff --git a/openbook_posts/views/post/serializers.py b/openbook_posts/views/post/serializers.py index 4806c5cd..3a5ec156 100644 --- a/openbook_posts/views/post/serializers.py +++ b/openbook_posts/views/post/serializers.py @@ -6,7 +6,7 @@ from openbook_common.models import Badge, Language from openbook_common.serializers import CommonHashtagSerializer from openbook_common.serializers_fields.post import PostCreatorField, PostReactionsEmojiCountField, ReactionField, \ - CommentsCountField, CirclesField, PostIsMutedField, IsEncircledField + CommentsCountField, CirclesField, PostIsMutedField, IsEncircledField, CommonPostNotificationsSubscriptionField from openbook_communities.models import CommunityMembership, Community from openbook_communities.serializers_fields import CommunityMembershipsField from openbook_posts.models import PostImage, Post, PostNotificationsSubscription @@ -153,6 +153,7 @@ class GetPostPostSerializer(serializers.ModelSerializer): language = PostLanguageSerializer() is_encircled = IsEncircledField() hashtags = CommonHashtagSerializer(many=True) + post_notifications_subscription = CommonPostNotificationsSubscriptionField() class Meta: model = Post @@ -178,6 +179,7 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', + 'post_notifications_subscription', ) @@ -219,10 +221,8 @@ class PostNotificationsSubscriptionSettingsSerializer(serializers.Serializer): required=True, ) comment_notifications = serializers.BooleanField(required=False) - comment_reaction_notifications = serializers.BooleanField(required=False) reaction_notifications = serializers.BooleanField(required=False) reply_notifications = serializers.BooleanField(required=False) - reply_where_commented_notifications = serializers.BooleanField(required=False) class UpdatePostNotificationsSubscriptionSettingsSerializer(serializers.Serializer): @@ -231,10 +231,8 @@ class UpdatePostNotificationsSubscriptionSettingsSerializer(serializers.Serializ required=True, ) comment_notifications = serializers.NullBooleanField(required=False, default=None) - comment_reaction_notifications = serializers.NullBooleanField(required=False, default=None) reaction_notifications = serializers.NullBooleanField(required=False, default=None) reply_notifications = serializers.NullBooleanField(required=False, default=None) - reply_where_commented_notifications = serializers.NullBooleanField(required=False, default=None) class OpenPostSerializer(serializers.Serializer): @@ -315,12 +313,14 @@ class Meta: class PostNotificationsSubscriptionSettingsPostSerializer(serializers.ModelSerializer): + post_notifications_subscription = CommonPostNotificationsSubscriptionField() class Meta: model = Post fields = ( 'id', 'uuid', + 'post_notifications_subscription' ) @@ -332,10 +332,8 @@ class Meta: 'id', 'post', 'comment_notifications', - 'comment_reaction_notifications', 'reaction_notifications', 'reply_notifications', - 'reply_where_commented_notifications', ) diff --git a/openbook_posts/views/post/views.py b/openbook_posts/views/post/views.py index 98f44298..6ae8707a 100644 --- a/openbook_posts/views/post/views.py +++ b/openbook_posts/views/post/views.py @@ -14,8 +14,9 @@ OpenClosePostSerializer, \ OpenPostSerializer, ClosePostSerializer, TranslatePostSerializer, \ SearchPostParticipantsSerializer, PostParticipantSerializer, GetPostParticipantsSerializer, PublishPostSerializer, \ - GetPostStatusSerializer, PostNotificationsSubscriptionSettingsSerializer, PostNotificationsSubscriptionSettingsResponseSerializer, \ - UpdatePostNotificationsSubscriptionSettingsSerializer + GetPostStatusSerializer, PostNotificationsSubscriptionSettingsSerializer, \ + PostNotificationsSubscriptionSettingsResponseSerializer, \ + UpdatePostNotificationsSubscriptionSettingsSerializer, PostNotificationsSubscriptionSettingsPostSerializer from openbook_translation.strategies.base import TranslationClientError, UnsupportedLanguagePairException, \ MaxTextLengthExceededError @@ -337,24 +338,20 @@ def put(self, request, post_uuid): data = serializer.validated_data post_uuid = data.get('post_uuid') comment_notifications = data.get('comment_notifications') - comment_reaction_notifications = data.get('comment_reaction_notifications') reaction_notifications = data.get('reaction_notifications') reply_notifications = data.get('reply_notifications') - reply_where_commented_notifications = data.get('reply_where_commented_notifications') post_id = get_post_id_for_post_uuid(post_uuid) with transaction.atomic(): post_notifications_subscription = user.create_post_notifications_subscription_for_post_with_id( post_id=post_id, comment_notifications=comment_notifications, - comment_reaction_notifications=comment_reaction_notifications, reaction_notifications=reaction_notifications, - reply_notifications=reply_notifications, - reply_where_commented_notifications=reply_where_commented_notifications + reply_notifications=reply_notifications ) - response_serializer = PostNotificationsSubscriptionSettingsResponseSerializer(post_notifications_subscription, - context={"request": request}) + response_serializer = PostNotificationsSubscriptionSettingsPostSerializer(post_notifications_subscription.post, + context={"request": request}) return Response(response_serializer.data, status=status.HTTP_201_CREATED) @@ -369,23 +366,19 @@ def patch(self, request, post_uuid): data = serializer.validated_data post_uuid = data.get('post_uuid') comment_notifications = data.get('comment_notifications') - comment_reaction_notifications = data.get('comment_reaction_notifications') reaction_notifications = data.get('reaction_notifications') reply_notifications = data.get('reply_notifications') - reply_where_commented_notifications = data.get('reply_where_commented_notifications') post_id = get_post_id_for_post_uuid(post_uuid) with transaction.atomic(): post_notifications_subscription = user.update_post_notifications_subscription_for_post_with_id( post_id=post_id, comment_notifications=comment_notifications, - comment_reaction_notifications=comment_reaction_notifications, reaction_notifications=reaction_notifications, - reply_notifications=reply_notifications, - reply_where_commented_notifications=reply_where_commented_notifications + reply_notifications=reply_notifications ) - response_serializer = PostNotificationsSubscriptionSettingsResponseSerializer(post_notifications_subscription, - context={"request": request}) + response_serializer = PostNotificationsSubscriptionSettingsPostSerializer(post_notifications_subscription.post, + context={"request": request}) return Response(response_serializer.data, status=status.HTTP_200_OK) diff --git a/openbook_posts/views/post_comment/serializers.py b/openbook_posts/views/post_comment/serializers.py index 98a1223f..712211d5 100644 --- a/openbook_posts/views/post_comment/serializers.py +++ b/openbook_posts/views/post_comment/serializers.py @@ -7,7 +7,7 @@ from openbook_common.serializers_fields.post_comment import PostCommenterField, PostCommentReactionsEmojiCountField, \ PostCommentReactionField, PostCommentIsMutedField, RepliesCountField from openbook_communities.models import CommunityMembership -from openbook_posts.models import PostComment, PostCommentReaction +from openbook_posts.models import PostComment, PostCommentReaction, PostCommentNotificationsSubscription from openbook_posts.validators import post_comment_id_exists, post_uuid_exists, \ post_comment_id_exists_for_post_with_uuid from openbook_posts.views.posts.serializers import PostLanguageSerializer @@ -202,3 +202,41 @@ class Meta: 'hashtags', 'id' ) + + +class PostCommentNotificationsSubscriptionSettingsSerializer(serializers.Serializer): + post_uuid = serializers.UUIDField( + validators=[post_uuid_exists], + required=True, + ) + post_comment_id = serializers.IntegerField( + validators=[post_comment_id_exists], + required=True, + ) + reaction_notifications = serializers.BooleanField(required=False) + reply_notifications = serializers.BooleanField(required=False) + + +class UpdatePostCommentNotificationsSubscriptionSettingsSerializer(serializers.Serializer): + post_uuid = serializers.UUIDField( + validators=[post_uuid_exists], + required=True, + ) + post_comment_id = serializers.IntegerField( + validators=[post_comment_id_exists], + required=True, + ) + reaction_notifications = serializers.NullBooleanField(required=False, default=None) + reply_notifications = serializers.NullBooleanField(required=False, default=None) + + +class PostCommentNotificationsSubscriptionSettingsResponseSerializer(serializers.ModelSerializer): + + class Meta: + model = PostCommentNotificationsSubscription + fields = ( + 'id', + 'post_comment', + 'reaction_notifications', + 'reply_notifications', + ) \ No newline at end of file diff --git a/openbook_posts/views/post_comment/views.py b/openbook_posts/views/post_comment/views.py index 134377f0..9584be69 100644 --- a/openbook_posts/views/post_comment/views.py +++ b/openbook_posts/views/post_comment/views.py @@ -10,7 +10,9 @@ from openbook_moderation.permissions import IsNotSuspended from openbook_posts.views.post_comment.serializers import DeletePostCommentSerializer, UpdatePostCommentSerializer, \ EditPostCommentSerializer, MutePostCommentSerializer, UnmutePostCommentSerializer, TranslatePostCommentSerializer, \ - GetPostCommentSerializer, GetPostCommentRequestSerializer + GetPostCommentSerializer, GetPostCommentRequestSerializer, PostCommentNotificationsSubscriptionSettingsSerializer, \ + PostCommentNotificationsSubscriptionSettingsResponseSerializer, \ + UpdatePostCommentNotificationsSubscriptionSettingsSerializer from openbook_translation.strategies.base import UnsupportedLanguagePairException, TranslationClientError, \ MaxTextLengthExceededError @@ -121,6 +123,64 @@ def post(self, request, post_uuid, post_comment_id): return ApiMessageResponse(message=_('Post comment unmuted.'), status=status.HTTP_200_OK) +class PostCommentNotificationsSubscriptionSettings(APIView): + permission_classes = (IsAuthenticated,) + + def put(self, request, post_uuid, post_comment_id): + request_data = request.data.copy() + request_data['post_uuid'] = post_uuid + request_data['post_comment_id'] = post_comment_id + + serializer = PostCommentNotificationsSubscriptionSettingsSerializer(data=request_data) + serializer.is_valid(raise_exception=True) + + user = request.user + data = serializer.validated_data + post_comment_id = data.get('post_comment_id') + reaction_notifications = data.get('reaction_notifications') + reply_notifications = data.get('reply_notifications') + + with transaction.atomic(): + post_comment_notifications_subscription = \ + user.create_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment_id, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications + ) + + response_serializer = PostCommentNotificationsSubscriptionSettingsResponseSerializer( + post_comment_notifications_subscription, context={"request": request}) + + return Response(response_serializer.data, status=status.HTTP_201_CREATED) + + def patch(self, request, post_uuid, post_comment_id): + request_data = request.data.copy() + request_data['post_uuid'] = post_uuid + request_data['post_comment_id'] = post_comment_id + + serializer = PostCommentNotificationsSubscriptionSettingsSerializer(data=request_data) + serializer.is_valid(raise_exception=True) + + user = request.user + data = serializer.validated_data + post_comment_id = data.get('post_comment_id') + reaction_notifications = data.get('reaction_notifications') + reply_notifications = data.get('reply_notifications') + + with transaction.atomic(): + post_comment_notifications_subscription = \ + user.update_post_comment_notifications_subscription_for_comment_with_id( + post_comment_id=post_comment_id, + reaction_notifications=reaction_notifications, + reply_notifications=reply_notifications + ) + + response_serializer = PostCommentNotificationsSubscriptionSettingsResponseSerializer( + post_comment_notifications_subscription, context={"request": request}) + + return Response(response_serializer.data, status=status.HTTP_200_OK) + + class TranslatePostComment(APIView): permission_classes = (IsAuthenticated,) diff --git a/openbook_posts/views/posts/serializers.py b/openbook_posts/views/posts/serializers.py index 75b94b4d..b031729c 100644 --- a/openbook_posts/views/posts/serializers.py +++ b/openbook_posts/views/posts/serializers.py @@ -8,7 +8,7 @@ from openbook_common.models import Emoji, Badge from openbook_common.serializers import CommonHashtagSerializer from openbook_common.serializers_fields.post import ReactionField, CommentsCountField, PostReactionsEmojiCountField, \ - CirclesField, PostCreatorField, PostIsMutedField, IsEncircledField + CirclesField, PostCreatorField, PostIsMutedField, IsEncircledField, CommonPostNotificationsSubscriptionField from openbook_common.serializers_fields.request import RestrictedImageFileSizeField, RestrictedFileSizeField from openbook_common.models import Language from openbook_communities.models import Community, CommunityMembership @@ -234,6 +234,7 @@ class AuthenticatedUserPostSerializer(serializers.ModelSerializer): language = PostLanguageSerializer() is_encircled = IsEncircledField() hashtags = CommonHashtagSerializer(many=True) + post_notifications_subscription = CommonPostNotificationsSubscriptionField() class Meta: model = Post @@ -259,6 +260,7 @@ class Meta: 'media_width', 'media_thumbnail', 'hashtags', + 'post_notifications_subscription', ) From c274447bbb11beac96decfab7b5321889cd39776 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 14 Apr 2020 19:04:09 +0200 Subject: [PATCH 32/40] :sparkles: add post notification subscription field to post serializer --- openbook_common/serializers.py | 15 ++++++++++++++- openbook_common/serializers_fields/post.py | 19 +++++++++++++++++++ .../views/community/posts/serializers.py | 9 ++++++--- .../views/hashtag/serializers.py | 7 +++++-- openbook_moderation/serializers.py | 4 +++- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/openbook_common/serializers.py b/openbook_common/serializers.py index b0d0955b..0a5f8dc7 100644 --- a/openbook_common/serializers.py +++ b/openbook_common/serializers.py @@ -8,7 +8,7 @@ from openbook_communities.serializers_fields import IsFavoriteField, CommunityMembershipsField from openbook_communities.validators import community_name_characters_validator, community_name_exists from openbook_hashtags.models import Hashtag -from openbook_posts.models import PostReaction, PostImage +from openbook_posts.models import PostReaction, PostImage, PostNotificationsSubscription class CommonEmojiSerializer(serializers.ModelSerializer): @@ -236,3 +236,16 @@ class ProxyDomainCheckSerializer(serializers.Serializer): url = serializers.CharField( required=True, ) + + +class CommonPostNotificationsSubscriptionSerializer(serializers.ModelSerializer): + + class Meta: + model = PostNotificationsSubscription + fields = ( + 'id', + 'post', + 'comment_notifications', + 'reaction_notifications', + 'reply_notifications', + ) \ No newline at end of file diff --git a/openbook_common/serializers_fields/post.py b/openbook_common/serializers_fields/post.py index a8043745..ea58b015 100644 --- a/openbook_common/serializers_fields/post.py +++ b/openbook_common/serializers_fields/post.py @@ -1,5 +1,6 @@ from rest_framework.fields import Field +from openbook_common.serializers import CommonPostNotificationsSubscriptionSerializer from openbook_common.utils.model_loaders import get_post_model from openbook_communities.models import CommunityMembership from openbook_posts.models import PostReaction, PostCommentReaction @@ -158,3 +159,21 @@ def to_representation(self, post): return is_encircled + +class CommonPostNotificationsSubscriptionField(Field): + + def __init__(self, **kwargs): + kwargs['source'] = '*' + kwargs['read_only'] = True + super(CommonPostNotificationsSubscriptionField, self).__init__(**kwargs) + + def to_representation(self, post): + request = self.context.get('request') + request_user = request.user + post_notifications_subscription_serializer = None + if request_user.post_notifications_subscriptions.filter(post=post).exists(): + post_notifications_subscription = request_user.post_notifications_subscriptions.get(post=post) + post_notifications_subscription_serializer = CommonPostNotificationsSubscriptionSerializer( + post_notifications_subscription, context={"request": request}).data + + return post_notifications_subscription_serializer diff --git a/openbook_communities/views/community/posts/serializers.py b/openbook_communities/views/community/posts/serializers.py index 9c19345f..227c6253 100644 --- a/openbook_communities/views/community/posts/serializers.py +++ b/openbook_communities/views/community/posts/serializers.py @@ -3,10 +3,11 @@ from openbook_common.serializers import CommonPostImageSerializer, CommonPostCreatorSerializer, \ CommonCommunityMembershipSerializer, CommonPostEmojiCountSerializer, CommonPostCommunitySerializer, \ - CommonPostReactionSerializer, CommonPostLanguageSerializer, CommonHashtagSerializer + CommonPostReactionSerializer, CommonPostLanguageSerializer, CommonHashtagSerializer, \ + CommonPostNotificationsSubscriptionSerializer from openbook_common.serializers_fields.community import CommunityPostsCountField from openbook_common.serializers_fields.post import PostReactionsEmojiCountField, CommentsCountField, PostCreatorField, \ - PostIsMutedField, ReactionField + PostIsMutedField, ReactionField, CommonPostNotificationsSubscriptionField from openbook_common.serializers_fields.request import RestrictedImageFileSizeField from openbook_communities.models import Community from openbook_communities.validators import community_name_characters_validator, community_name_exists @@ -52,6 +53,7 @@ class CommunityPostSerializer(serializers.ModelSerializer): reaction = ReactionField(reaction_serializer=CommonPostReactionSerializer) language = CommonPostLanguageSerializer() hashtags = CommonHashtagSerializer(many=True) + post_notifications_subscription = CommonPostNotificationsSubscriptionField() class Meta: model = Post @@ -75,7 +77,8 @@ class Meta: 'media_height', 'media_width', 'media_thumbnail', - 'hashtags' + 'hashtags', + 'post_notifications_subscription' ) diff --git a/openbook_hashtags/views/hashtag/serializers.py b/openbook_hashtags/views/hashtag/serializers.py index b7c376de..9f4d1334 100644 --- a/openbook_hashtags/views/hashtag/serializers.py +++ b/openbook_hashtags/views/hashtag/serializers.py @@ -7,7 +7,8 @@ CommonEmojiSerializer from openbook_common.serializers_fields.hashtag import HashtagPostsCountField, IsHashtagReportedField from openbook_common.serializers_fields.post import ReactionField, CommentsCountField, PostCreatorField, \ - PostReactionsEmojiCountField, PostIsMutedField, IsEncircledField, CirclesField + PostReactionsEmojiCountField, PostIsMutedField, IsEncircledField, CirclesField, \ + CommonPostNotificationsSubscriptionField from openbook_hashtags.models import Hashtag from openbook_hashtags.validators import hashtag_name_exists from openbook_posts.models import Post @@ -65,6 +66,7 @@ class GetHashtagPostsPostSerializer(serializers.ModelSerializer): hashtags = CommonHashtagSerializer(many=True) is_encircled = IsEncircledField() circles = CirclesField(circle_serializer=CommonCircleSerializer) + post_notifications_subscription = CommonPostNotificationsSubscriptionField() class Meta: model = Post @@ -88,5 +90,6 @@ class Meta: 'media_width', 'media_thumbnail', 'hashtags', - 'circles' + 'circles', + 'post_notifications_subscription' ) diff --git a/openbook_moderation/serializers.py b/openbook_moderation/serializers.py index 4d651ab5..3996b6f7 100644 --- a/openbook_moderation/serializers.py +++ b/openbook_moderation/serializers.py @@ -5,7 +5,7 @@ from openbook_common.models import Language, Badge from openbook_common.serializers import CommonEmojiSerializer from openbook_common.serializers_fields.hashtag import HashtagPostsCountField -from openbook_common.serializers_fields.post import IsEncircledField +from openbook_common.serializers_fields.post import IsEncircledField, CommonPostNotificationsSubscriptionField from openbook_communities.models import Community from openbook_hashtags.models import Hashtag from openbook_moderation.models import ModeratedObject, ModerationCategory @@ -114,6 +114,7 @@ class ModeratedObjectPostSerializer(serializers.ModelSerializer): image = ModeratedObjectPostImageSerializer() language = LanguageSerializer() is_encircled = IsEncircledField() + post_notifications_subscription = CommonPostNotificationsSubscriptionField() class Meta: model = Post @@ -128,6 +129,7 @@ class Meta: 'comments_enabled', 'is_closed', 'is_encircled', + 'post_notifications_subscription' ) From baece90b81fd5d2b7670bef397012f7d96eb979c Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 14 Apr 2020 19:39:05 +0200 Subject: [PATCH 33/40] :fire: remove legacy field --- ...s_post_subscription_comment_notifications.py | 17 +++++++++++++++++ openbook_auth/models.py | 11 ++--------- 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 openbook_auth/migrations/0053_remove_usernotificationssettings_post_subscription_comment_notifications.py diff --git a/openbook_auth/migrations/0053_remove_usernotificationssettings_post_subscription_comment_notifications.py b/openbook_auth/migrations/0053_remove_usernotificationssettings_post_subscription_comment_notifications.py new file mode 100644 index 00000000..c85311c3 --- /dev/null +++ b/openbook_auth/migrations/0053_remove_usernotificationssettings_post_subscription_comment_notifications.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.10 on 2020-04-14 17:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_auth', '0052_usernotificationssettings_post_subscription_comment_notifications'), + ] + + operations = [ + migrations.RemoveField( + model_name='usernotificationssettings', + name='post_subscription_comment_notifications', + ), + ] diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 6f886d63..8e145aef 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -533,7 +533,6 @@ def update_notifications_settings(self, post_comment_notifications=None, post_re post_comment_reply_notifications=None, post_comment_user_mention_notifications=None, post_user_mention_notifications=None, - post_subscription_comment_notifications=None, ): notifications_settings = self.notifications_settings @@ -550,8 +549,7 @@ def update_notifications_settings(self, post_comment_notifications=None, post_re post_comment_reaction_notifications=post_comment_reaction_notifications, post_comment_reply_notifications=post_comment_reply_notifications, post_comment_user_mention_notifications=post_comment_user_mention_notifications, - post_user_mention_notifications=post_user_mention_notifications, - post_subscription_comment_notifications=post_subscription_comment_notifications, + post_user_mention_notifications=post_user_mention_notifications ) return notifications_settings @@ -3842,7 +3840,6 @@ class UserNotificationsSettings(models.Model): post_comment_user_mention_notifications = models.BooleanField(_('post comment user mention notifications'), default=True) post_user_mention_notifications = models.BooleanField(_('post user mention notifications'), default=True) - post_subscription_comment_notifications = models.BooleanField(_('post subscription comment notifications'), default=True) @classmethod def create_notifications_settings(cls, user): @@ -3859,8 +3856,7 @@ def update(self, post_comment_notifications=None, user_new_post_notifications=None, post_comment_user_mention_notifications=None, post_user_mention_notifications=None, - post_comment_reaction_notifications=None, - post_subscription_comment_notifications=None, ): + post_comment_reaction_notifications=None): if post_comment_notifications is not None: self.post_comment_notifications = post_comment_notifications @@ -3898,9 +3894,6 @@ def update(self, post_comment_notifications=None, if user_new_post_notifications is not None: self.user_new_post_notifications = user_new_post_notifications - if post_subscription_comment_notifications is not None: - self.post_subscription_comment_notifications = post_subscription_comment_notifications - self.save() From e38d1d9e1c3a595afb944a8796995994fc043151 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 14 Apr 2020 19:39:41 +0200 Subject: [PATCH 34/40] :recycle: more refactoring --- .../tests/views/test_authenticated_user.py | 5 ----- openbook_auth/views/auth/serializers.py | 4 +--- openbook_auth/views/authenticated_user/views.py | 4 +--- .../tests/views/moderated_object.py | 14 +++++++++----- .../tests/views/test_post_comment_reactions.py | 3 +-- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/openbook_auth/tests/views/test_authenticated_user.py b/openbook_auth/tests/views/test_authenticated_user.py index 7270a6e0..fc6d5eab 100644 --- a/openbook_auth/tests/views/test_authenticated_user.py +++ b/openbook_auth/tests/views/test_authenticated_user.py @@ -601,7 +601,6 @@ def test_can_update_notifications_settings(self): notifications_settings.post_comment_reaction_notifications = fake.boolean() notifications_settings.post_comment_user_mention_notifications = fake.boolean() notifications_settings.post_user_mention_notifications = fake.boolean() - notifications_settings.post_subscription_comment_notifications = fake.boolean() notifications_settings.save() @@ -619,7 +618,6 @@ def test_can_update_notifications_settings(self): new_post_comment_reply_notifications = not notifications_settings.post_comment_reply_notifications new_post_comment_user_mention_notifications = not notifications_settings.post_comment_user_mention_notifications new_post_user_mention_notifications = not notifications_settings.post_user_mention_notifications - new_post_subscription_comment_notifications = not notifications_settings.post_subscription_comment_notifications data = { 'post_comment_notifications': new_post_comment_notifications, @@ -634,7 +632,6 @@ def test_can_update_notifications_settings(self): 'post_comment_reaction_notifications': new_post_comment_reaction_notifications, 'post_comment_user_mention_notifications': new_post_comment_user_mention_notifications, 'post_user_mention_notifications': new_post_user_mention_notifications, - 'post_subscription_comment_notifications': new_post_subscription_comment_notifications } url = self._get_url() @@ -658,8 +655,6 @@ def test_can_update_notifications_settings(self): new_post_comment_reply_notifications) self.assertEqual(notifications_settings.post_comment_reaction_notifications, new_post_comment_reaction_notifications) - self.assertEqual(notifications_settings.post_subscription_comment_notifications, - new_post_subscription_comment_notifications) def _get_url(self): return reverse('authenticated-user-notifications-settings') diff --git a/openbook_auth/views/auth/serializers.py b/openbook_auth/views/auth/serializers.py index 6ea53dc6..5bdb2f60 100644 --- a/openbook_auth/views/auth/serializers.py +++ b/openbook_auth/views/auth/serializers.py @@ -69,8 +69,7 @@ class Meta: 'post_comment_reaction_notifications', 'post_comment_reply_notifications', 'post_comment_user_mention_notifications', - 'post_user_mention_notifications', - 'post_subscription_comment_notifications', + 'post_user_mention_notifications' ) @@ -87,7 +86,6 @@ class UpdateAuthenticatedUserNotificationsSettingsSerializer(serializers.Seriali post_comment_reply_notifications = serializers.BooleanField(required=False) post_comment_user_mention_notifications = serializers.BooleanField(required=False) post_user_mention_notifications = serializers.BooleanField(required=False) - post_subscription_comment_notifications = serializers.BooleanField(required=False) class RequestPasswordResetSerializer(serializers.Serializer): diff --git a/openbook_auth/views/authenticated_user/views.py b/openbook_auth/views/authenticated_user/views.py index 8c0bfdc5..585f1598 100644 --- a/openbook_auth/views/authenticated_user/views.py +++ b/openbook_auth/views/authenticated_user/views.py @@ -112,7 +112,6 @@ def patch(self, request): post_comment_reply_notifications = data.get('post_comment_reply_notifications') post_comment_user_mention_notifications = data.get('post_comment_user_mention_notifications') post_user_mention_notifications = data.get('post_user_mention_notifications') - post_subscription_comment_notifications = data.get('post_subscription_comment_notifications') user = request.user @@ -129,8 +128,7 @@ def patch(self, request): post_comment_reaction_notifications=post_comment_reaction_notifications, post_comment_reply_notifications=post_comment_reply_notifications, post_comment_user_mention_notifications=post_comment_user_mention_notifications, - post_user_mention_notifications=post_user_mention_notifications, - post_subscription_comment_notifications=post_subscription_comment_notifications + post_user_mention_notifications=post_user_mention_notifications ) user_notifications_settings_serializer = AuthenticatedUserNotificationsSettingsSerializer( diff --git a/openbook_moderation/tests/views/moderated_object.py b/openbook_moderation/tests/views/moderated_object.py index 3dd3208c..99f03926 100644 --- a/openbook_moderation/tests/views/moderated_object.py +++ b/openbook_moderation/tests/views/moderated_object.py @@ -1431,9 +1431,9 @@ def test_approving_community_post_moderated_object_deletes_comment_notifications self.assertFalse(PostCommentNotification.objects.filter( post_comment=community_post_comment).exists()) - def test_approving_community_post_moderated_object_deletes_post_subscription_comment_notifications_for_comment(self): + def test_approving_community_post_moderated_object_deletes_post_subscribers_comment_notifications_for_comment(self): """ - should delete post subscription comment notifications for comments on approving community_post moderated object + should delete post subscribers notifications for comments on approving community_post moderated object """ global_moderator = make_global_moderator() @@ -1441,15 +1441,19 @@ def test_approving_community_post_moderated_object_deletes_post_subscription_com community_post_creator = make_user() community_post_commenter = make_user() - community_post_replier = make_user() + community_post_subscriber = make_user() community_post_creator.join_community_with_name(community_name=community.name) community_post_commenter.join_community_with_name(community_name=community.name) - community_post_replier.join_community_with_name(community_name=community.name) + community_post_subscriber.join_community_with_name(community_name=community.name) community_post = community_post_creator.create_community_post( community_name=community.name, text=make_fake_post_text()) + + community_post_subscriber.create_post_notifications_subscription_for_post_with_id( + post_id=community_post.id, + comment_notifications=True) community_post_comment = community_post_commenter.comment_post( post=community_post, text=make_fake_post_text()) @@ -1472,7 +1476,7 @@ def test_approving_community_post_moderated_object_deletes_post_subscription_com self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(PostCommentNotification.objects.filter( - post_comment=community_post_comment).exists()) + post_comment=community_post_comment, notification__owner=community_post_subscriber).exists()) def test_approving_community_post_moderated_object_deletes_comment_reply_notifications(self): """ diff --git a/openbook_posts/tests/views/test_post_comment_reactions.py b/openbook_posts/tests/views/test_post_comment_reactions.py index df65c267..b8c7bed4 100644 --- a/openbook_posts/tests/views/test_post_comment_reactions.py +++ b/openbook_posts/tests/views/test_post_comment_reactions.py @@ -703,8 +703,7 @@ def test_reacting_on_foreign_post_comment_doesnt_send_push_notification_when_pos # mute post notifications user.update_post_notifications_subscription_for_post_with_id( post_id=post.pk, - reaction_notifications=False, - comment_reaction_notifications=False) + reaction_notifications=True) emoji_group = make_reactions_emoji_group() From 324151b4ef77c2628c9299bf387399a864b6e91a Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 14 Apr 2020 23:12:03 +0200 Subject: [PATCH 35/40] :white_check_mark: fix more tests --- .../views/test_post_comment_reactions.py | 11 ++----- .../tests/views/test_post_comment_replies.py | 29 ++++--------------- .../tests/views/test_post_comments.py | 5 +--- 3 files changed, 8 insertions(+), 37 deletions(-) diff --git a/openbook_posts/tests/views/test_post_comment_reactions.py b/openbook_posts/tests/views/test_post_comment_reactions.py index b8c7bed4..ddbab587 100644 --- a/openbook_posts/tests/views/test_post_comment_reactions.py +++ b/openbook_posts/tests/views/test_post_comment_reactions.py @@ -671,10 +671,7 @@ def test_reacting_on_foreign_post_comment_doesnt_send_push_notification_when_mut post_comment = user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) # mute post comment - user.update_post_comment_notifications_subscription_for_comment_with_id( - post_comment_id=post_comment.pk, - reaction_notifications=False, - reply_notifications=False) + user.mute_post_comment_with_id(post_comment_id=post_comment.pk) emoji_group = make_reactions_emoji_group() @@ -699,11 +696,7 @@ def test_reacting_on_foreign_post_comment_doesnt_send_push_notification_when_pos headers = make_authentication_headers_for_user(reactor) post = user.create_public_post(text=make_fake_post_text()) post_comment = user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - - # mute post notifications - user.update_post_notifications_subscription_for_post_with_id( - post_id=post.pk, - reaction_notifications=True) + user.mute_post_with_id(post_id=post.pk) emoji_group = make_reactions_emoji_group() diff --git a/openbook_posts/tests/views/test_post_comment_replies.py b/openbook_posts/tests/views/test_post_comment_replies.py index 7520f58c..67954b4c 100644 --- a/openbook_posts/tests/views/test_post_comment_replies.py +++ b/openbook_posts/tests/views/test_post_comment_replies.py @@ -1385,11 +1385,7 @@ def test_commenting_reply_in_commented_post_by_foreign_user_creates_foreign_noti foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - # mute post notifications - foreign_user.update_post_notifications_subscription_for_post_with_id( - post_id=post.pk, - comment_notifications=False, - reply_notifications=False) + foreign_user.mute_post_with_id(post_id=post.pk) reply_comment_text = make_fake_post_comment_text() @@ -1519,11 +1515,7 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_commenter(s post_uuid=post.uuid, text=make_fake_post_comment_text()) - # mute post notifications - foreign_user.update_post_notifications_subscription_for_post_with_id( - post_id=post.pk, - comment_notifications=False, - reply_notifications=False) + foreign_user.mute_post(post=post) reply_comment_text = make_fake_post_comment_text() @@ -1567,11 +1559,7 @@ def test_replying_on_foreign_post_comment_sends_push_notification_to_other_repli post_uuid=post.uuid, text=make_fake_post_comment_text()) - # mute post notifications - post_creator.update_post_notifications_subscription_for_post_with_id( - post_id=post.pk, - comment_notifications=False, - reply_notifications=False) + post_creator.mute_post(post=post) reply_comment_text = make_fake_post_comment_text() @@ -1607,18 +1595,12 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_mut post = post_creator.create_public_post(text=make_fake_post_text()) post_comment = post_creator.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - # mute post comment for creator - post_creator.update_post_comment_notifications_subscription_for_comment_with_id( - post_comment_id=post_comment.pk, - reply_notifications=False) + post_creator.mute_post_comment_with_id(post_comment_id=post_comment.pk) foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - # mute post comment for foreign user - foreign_user.update_post_comment_notifications_subscription_for_comment_with_id( - post_comment_id=post_comment.pk, - reply_notifications=False) + foreign_user.mute_post_comment_with_id(post_comment_id=post_comment.pk) send_post_comment_reply_push_notification_call.reset_mock() @@ -1653,7 +1635,6 @@ def test_replying_on_foreign_post_comment_doesnt_send_push_notification_when_pos foreign_user.reply_to_comment_with_id_for_post_with_uuid(post_comment_id=post_comment.pk, post_uuid=post.uuid, text=make_fake_post_comment_text()) - foreign_user.mute_post_with_id(post_id=post.pk) send_post_comment_reply_push_notification_call.reset_mock() diff --git a/openbook_posts/tests/views/test_post_comments.py b/openbook_posts/tests/views/test_post_comments.py index 0ace9091..febacd8b 100644 --- a/openbook_posts/tests/views/test_post_comments.py +++ b/openbook_posts/tests/views/test_post_comments.py @@ -1540,10 +1540,7 @@ def test_commenting_in_commented_post_by_foreign_user_creates_foreign_notificati foreign_user.comment_post_with_id(post_id=post.pk, text=make_fake_post_comment_text()) - foreign_user.update_post_notifications_subscription_for_post_with_id( - post_id=post.pk, - comment_notifications=False - ) + foreign_user.mute_post_with_id(post_id=post.pk) post_comment_text = make_fake_post_comment_text() From 8dbc5c3e41c1299aeb20bf95ebf75d5e5baee428 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Fri, 17 Apr 2020 09:54:13 +0200 Subject: [PATCH 36/40] :sparkles: migration script for notification subs --- openbook_common/utils/helpers.py | 24 +++++ openbook_posts/jobs.py | 32 +------ .../commands/migrate_post_notifications.py | 91 +++++++++++++++++++ 3 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 openbook_posts/management/commands/migrate_post_notifications.py diff --git a/openbook_common/utils/helpers.py b/openbook_common/utils/helpers.py index 19478b46..467a2f42 100644 --- a/openbook_common/utils/helpers.py +++ b/openbook_common/utils/helpers.py @@ -7,6 +7,7 @@ import magic import spectra +from cursor_pagination import CursorPaginator from django.http import QueryDict from imagekit.utils import get_cache from imagekit.models import ProcessedImageField @@ -143,3 +144,26 @@ def write_in_memory_file_to_disk(in_memory_file): tmp_file.seek(0) tmp_file.close() return tmp_file + + +def chunked_queryset_iterator(queryset, size, *, ordering=('id',)): + """ + Split a queryset into chunks. + This can be used instead of `queryset.iterator()`, + so `.prefetch_related()` also works + Note:: + The ordering must uniquely identify the object, + and be in the same order (ASC/DESC). See https://github.com/photocrowd/django-cursor-pagination + """ + pager = CursorPaginator(queryset, ordering) + after = None + while True: + page = pager.page(after=after, first=size) + if page: + yield from page.items + else: + return + if not page.has_next: + break + # take last item, next page starts after this. + after = pager.cursor(instance=page[-1]) diff --git a/openbook_posts/jobs.py b/openbook_posts/jobs.py index 2032d009..a35380f0 100644 --- a/openbook_posts/jobs.py +++ b/openbook_posts/jobs.py @@ -1,10 +1,11 @@ from django.utils import timezone from django_rq import job + +from openbook_common.utils.helpers import chunked_queryset_iterator from video_encoding import tasks from datetime import timedelta from django.db.models import Q, Count from django.conf import settings -from cursor_pagination import CursorPaginator from openbook_common.utils.model_loaders import get_post_model, get_post_media_model, get_community_model, \ get_top_post_model, get_post_comment_model, get_moderated_object_model, get_trending_post_model @@ -91,7 +92,7 @@ def curate_top_posts(): total_checked_posts = 0 total_curated_posts = 0 - for post in _chunked_queryset_iterator(posts, 1000): + for post in chunked_queryset_iterator(posts, 1000): total_checked_posts = total_checked_posts + 1 if not post.reactions_count >= settings.MIN_UNIQUE_TOP_POST_REACTIONS_COUNT: unique_comments_count = PostComment.objects.filter(post=post). \ @@ -178,7 +179,7 @@ def clean_top_posts(): delete_ids = [] - for top_post in _chunked_queryset_iterator(top_posts, 1000): + for top_post in chunked_queryset_iterator(top_posts, 1000): if not top_post.reactions_count >= settings.MIN_UNIQUE_TOP_POST_REACTIONS_COUNT: unique_comments_count = PostComment.objects.filter(post=top_post.post). \ values('commenter_id'). \ @@ -286,7 +287,7 @@ def bootstrap_trending_posts(): total_curated_posts = 0 total_checked_posts = 0 - for post in _chunked_queryset_iterator(posts, 1000): + for post in chunked_queryset_iterator(posts, 1000): total_checked_posts += 1 trending_post = TrendingPost(post=post, created=timezone.now()) trending_posts_objects.append(trending_post) @@ -345,26 +346,3 @@ def clean_trending_posts(): delete_ids = [trending_post.pk for trending_post in less_than_min_reactions_trending_posts] TrendingPost.objects.filter(id__in=delete_ids).delete() - - -def _chunked_queryset_iterator(queryset, size, *, ordering=('id',)): - """ - Split a queryset into chunks. - This can be used instead of `queryset.iterator()`, - so `.prefetch_related()` also works - Note:: - The ordering must uniquely identify the object, - and be in the same order (ASC/DESC). See https://github.com/photocrowd/django-cursor-pagination - """ - pager = CursorPaginator(queryset, ordering) - after = None - while True: - page = pager.page(after=after, first=size) - if page: - yield from page.items - else: - return - if not page.has_next: - break - # take last item, next page starts after this. - after = pager.cursor(instance=page[-1]) diff --git a/openbook_posts/management/commands/migrate_post_notifications.py b/openbook_posts/management/commands/migrate_post_notifications.py new file mode 100644 index 00000000..5c999d54 --- /dev/null +++ b/openbook_posts/management/commands/migrate_post_notifications.py @@ -0,0 +1,91 @@ +from django.core.management.base import BaseCommand +import logging + +from django.db import transaction + +from openbook_common.utils.helpers import chunked_queryset_iterator +from openbook_common.utils.model_loaders import get_post_model, get_post_notifications_subscription_model, \ + get_post_comment_model, get_post_comment_notifications_subscription_model + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = 'Creates PostNotificationsSubscription and ' \ + 'PostCommentNotificationsSubscription objects for each post/comment' + + def handle(self, *args, **options): + Post = get_post_model() + PostComment = get_post_comment_model() + + posts_to_migrate = Post.objects.only('id', 'creator').all() + migrated_posts = 0 + + for post in chunked_queryset_iterator(posts_to_migrate, 1000): + post_comments = PostComment.objects.select_related('post', 'commenter', 'parent_comment').\ + filter(post=post) + with transaction.atomic(): + self.create_notifications_subscription_for_creator(post) + for comment in post_comments: + if comment.parent_comment is None: + self.create_notifications_subscription_for_commenter(comment) + else: + # reply + self.create_notifications_subscription_for_replier(comment) + + migrated_posts = migrated_posts + 1 + + logger.info('Migrated %d posts' % migrated_posts) + + def create_notifications_subscription_for_creator(self, post): + PostNotificationsSubscription = get_post_notifications_subscription_model() + PostNotificationsSubscription.get_or_create_post_notifications_subscription( + post=post, + subscriber=post.creator, + comment_notifications=True, + reaction_notifications=True, + reply_notifications=False) + + def create_notifications_subscription_for_commenter(self, post_comment): + # notification subscription for post + PostNotificationsSubscription = get_post_notifications_subscription_model() + PostNotificationsSubscription.get_or_create_post_notifications_subscription( + post=post_comment.post, + subscriber=post_comment.commenter, + comment_notifications=True, + reaction_notifications=False, + reply_notifications=False) + # notification subscription for comment + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + PostCommentNotificationsSubscription.get_or_create_post_comment_notifications_subscription( + post_comment=post_comment, + subscriber=post_comment.commenter, + reaction_notifications=True, + reply_notifications=True + ) + + def create_notifications_subscription_for_replier(self, post_comment_reply): + # notification subscription for post + PostNotificationsSubscription = get_post_notifications_subscription_model() + PostNotificationsSubscription.get_or_create_post_notifications_subscription( + post=post_comment_reply.post, + subscriber=post_comment_reply.commenter, + comment_notifications=False, + reaction_notifications=False, + reply_notifications=False) + # notification subscription for parent comment + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + PostCommentNotificationsSubscription.get_or_create_post_comment_notifications_subscription( + post_comment=post_comment_reply.parent_comment, + subscriber=post_comment_reply.commenter, + reaction_notifications=False, + reply_notifications=True + ) + # notification subscription for reply + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + PostCommentNotificationsSubscription.get_or_create_post_comment_notifications_subscription( + post_comment=post_comment_reply, + subscriber=post_comment_reply.commenter, + reaction_notifications=True, + reply_notifications=True + ) From 22e5e7eeb33966d5fd63333640be98559b46e4c0 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Mon, 20 Apr 2020 18:07:58 +0200 Subject: [PATCH 37/40] :sparkles: add comment notification subscriptions to serializers --- Pipfile | 4 +++- Pipfile.lock | 18 +++++++++++++++- openbook_common/serializers.py | 17 +++++++++++++-- .../serializers_fields/post_comment.py | 21 +++++++++++++++++++ .../post_comment_replies/serializers.py | 6 ++++-- .../views/post_comment/serializers.py | 20 +++++++++++++++--- openbook_posts/views/post_comment/views.py | 14 ++++++++----- .../views/post_comments/serializers.py | 11 +++++++--- 8 files changed, 94 insertions(+), 17 deletions(-) diff --git a/Pipfile b/Pipfile index c3a04673..11e346dd 100644 --- a/Pipfile +++ b/Pipfile @@ -6,13 +6,13 @@ name = "pypi" [dev-packages] polib = "*" pytz = "*" +colorlog = "*" [packages] django = "==2.2.12" python-dotenv = "*" djangorestframework = "*" sentry-sdk = "==0.10.2" -mysqlclient = "*" pillow = "*" django-nose = "*" nose = "*" @@ -55,6 +55,8 @@ shutilwhich = "*" halo = "*" watchdog = "*" spectra = "*" +mysqlclient = "*" +colorlog = "*" [pipenv] allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index 50c96fa6..8140415d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0db9e3659f14e7c8aed38a746c746ba0aa3902d6b00947b2504b01d08bdb062d" + "sha256": "08e9aa637a9494fc6250c92d330dff771826b4a1783a2b8f483f9d548a23ae4c" }, "pipfile-spec": 6, "requires": {}, @@ -81,6 +81,14 @@ ], "version": "==0.4.3" }, + "colorlog": { + "hashes": [ + "sha256:30aaef5ab2a1873dec5da38fd6ba568fa761c9fa10b40241027fa3edea47f3d2", + "sha256:732c191ebbe9a353ec160d043d02c64ddef9028de8caae4cfa8bd49b6afed53e" + ], + "index": "pypi", + "version": "==4.1.0" + }, "colormath": { "hashes": [ "sha256:3d4605af344527da0e4f9f504fad7ddbebda35322c566a6c72e28edb1ff31217" @@ -816,6 +824,14 @@ } }, "develop": { + "colorlog": { + "hashes": [ + "sha256:30aaef5ab2a1873dec5da38fd6ba568fa761c9fa10b40241027fa3edea47f3d2", + "sha256:732c191ebbe9a353ec160d043d02c64ddef9028de8caae4cfa8bd49b6afed53e" + ], + "index": "pypi", + "version": "==4.1.0" + }, "polib": { "hashes": [ "sha256:93b730477c16380c9a96726c54016822ff81acfa553977fdd131f2b90ba858d7", diff --git a/openbook_common/serializers.py b/openbook_common/serializers.py index 0a5f8dc7..df525c96 100644 --- a/openbook_common/serializers.py +++ b/openbook_common/serializers.py @@ -8,7 +8,8 @@ from openbook_communities.serializers_fields import IsFavoriteField, CommunityMembershipsField from openbook_communities.validators import community_name_characters_validator, community_name_exists from openbook_hashtags.models import Hashtag -from openbook_posts.models import PostReaction, PostImage, PostNotificationsSubscription +from openbook_posts.models import PostReaction, PostImage, PostNotificationsSubscription, \ + PostCommentNotificationsSubscription class CommonEmojiSerializer(serializers.ModelSerializer): @@ -248,4 +249,16 @@ class Meta: 'comment_notifications', 'reaction_notifications', 'reply_notifications', - ) \ No newline at end of file + ) + + +class CommonPostCommentNotificationsSubscriptionSerializer(serializers.ModelSerializer): + + class Meta: + model = PostCommentNotificationsSubscription + fields = ( + 'id', + 'post_comment', + 'reaction_notifications', + 'reply_notifications', + ) diff --git a/openbook_common/serializers_fields/post_comment.py b/openbook_common/serializers_fields/post_comment.py index 2abf7b64..3bb65796 100644 --- a/openbook_common/serializers_fields/post_comment.py +++ b/openbook_common/serializers_fields/post_comment.py @@ -1,5 +1,6 @@ from rest_framework.fields import Field +from openbook_common.serializers import CommonPostCommentNotificationsSubscriptionSerializer from openbook_common.utils.model_loaders import get_post_comment_model from openbook_posts.models import PostCommentReaction @@ -113,3 +114,23 @@ def to_representation(self, post_comment): is_muted = request_user.has_muted_post_comment_with_id(post_comment_id=post_comment.pk) return is_muted + + +class CommonPostCommentNotificationsSubscriptionField(Field): + + def __init__(self, **kwargs): + kwargs['source'] = '*' + kwargs['read_only'] = True + super(CommonPostCommentNotificationsSubscriptionField, self).__init__(**kwargs) + + def to_representation(self, post_comment): + request = self.context.get('request') + request_user = request.user + post_comment_notifications_subscription_serializer = None + if request_user.post_comment_notifications_subscriptions.filter(post_comment=post_comment).exists(): + post_comment_notifications_subscription = request_user.post_comment_notifications_subscriptions.get( + post_comment=post_comment) + post_comment_notifications_subscription_serializer = CommonPostCommentNotificationsSubscriptionSerializer( + post_comment_notifications_subscription, context={"request": request}).data + + return post_comment_notifications_subscription_serializer diff --git a/openbook_posts/views/post_comment/post_comment_replies/serializers.py b/openbook_posts/views/post_comment/post_comment_replies/serializers.py index d5649b75..8b34bb0b 100644 --- a/openbook_posts/views/post_comment/post_comment_replies/serializers.py +++ b/openbook_posts/views/post_comment/post_comment_replies/serializers.py @@ -4,7 +4,7 @@ from openbook_common.models import Emoji from openbook_common.serializers import CommonHashtagSerializer from openbook_common.serializers_fields.post_comment import PostCommenterField, PostCommentReactionsEmojiCountField, \ - PostCommentReactionField, PostCommentIsMutedField + PostCommentReactionField, PostCommentIsMutedField, CommonPostCommentNotificationsSubscriptionField from openbook_posts.models import PostComment, PostCommentReaction from openbook_posts.validators import post_comment_id_exists, post_uuid_exists, post_comment_text_validators @@ -74,6 +74,7 @@ class PostCommentReplySerializer(serializers.ModelSerializer): is_muted = PostCommentIsMutedField() language = PostCommentLanguageSerializer() hashtags = CommonHashtagSerializer(many=True) + post_comment_notifications_subscription = CommonPostCommentNotificationsSubscriptionField() class Meta: model = PostComment @@ -88,7 +89,8 @@ class Meta: 'is_edited', 'is_muted', 'hashtags', - 'id' + 'id', + 'post_comment_notifications_subscription' ) diff --git a/openbook_posts/views/post_comment/serializers.py b/openbook_posts/views/post_comment/serializers.py index 712211d5..f27232f4 100644 --- a/openbook_posts/views/post_comment/serializers.py +++ b/openbook_posts/views/post_comment/serializers.py @@ -5,7 +5,8 @@ from openbook_common.models import Language, Emoji from openbook_common.serializers import CommonUserProfileBadgeSerializer, CommonHashtagSerializer from openbook_common.serializers_fields.post_comment import PostCommenterField, PostCommentReactionsEmojiCountField, \ - PostCommentReactionField, PostCommentIsMutedField, RepliesCountField + PostCommentReactionField, PostCommentIsMutedField, RepliesCountField, \ + CommonPostCommentNotificationsSubscriptionField from openbook_communities.models import CommunityMembership from openbook_posts.models import PostComment, PostCommentReaction, PostCommentNotificationsSubscription from openbook_posts.validators import post_comment_id_exists, post_uuid_exists, \ @@ -28,6 +29,17 @@ class Meta: ) +class MuteUnmutePostCommentResponseSerializer(serializers.ModelSerializer): + is_muted = PostCommentIsMutedField() + + class Meta: + model = PostComment + fields = ( + 'id', + 'is_muted', + ) + + class DeletePostCommentSerializer(serializers.Serializer): post_uuid = serializers.UUIDField( validators=[post_uuid_exists], @@ -186,6 +198,7 @@ class GetPostCommentSerializer(serializers.ModelSerializer): is_muted = PostCommentIsMutedField() language = PostCommentLanguageSerializer() hashtags = CommonHashtagSerializer(many=True) + post_comment_notifications_subscription = CommonPostCommentNotificationsSubscriptionField() class Meta: model = PostComment @@ -200,7 +213,8 @@ class Meta: 'is_edited', 'is_muted', 'hashtags', - 'id' + 'id', + 'post_comment_notifications_subscription' ) @@ -239,4 +253,4 @@ class Meta: 'post_comment', 'reaction_notifications', 'reply_notifications', - ) \ No newline at end of file + ) diff --git a/openbook_posts/views/post_comment/views.py b/openbook_posts/views/post_comment/views.py index 9584be69..b29835ea 100644 --- a/openbook_posts/views/post_comment/views.py +++ b/openbook_posts/views/post_comment/views.py @@ -12,7 +12,7 @@ EditPostCommentSerializer, MutePostCommentSerializer, UnmutePostCommentSerializer, TranslatePostCommentSerializer, \ GetPostCommentSerializer, GetPostCommentRequestSerializer, PostCommentNotificationsSubscriptionSettingsSerializer, \ PostCommentNotificationsSubscriptionSettingsResponseSerializer, \ - UpdatePostCommentNotificationsSubscriptionSettingsSerializer + UpdatePostCommentNotificationsSubscriptionSettingsSerializer, MuteUnmutePostCommentResponseSerializer from openbook_translation.strategies.base import UnsupportedLanguagePairException, TranslationClientError, \ MaxTextLengthExceededError @@ -100,9 +100,11 @@ def post(self, request, post_uuid, post_comment_id): user = request.user with transaction.atomic(): - user.mute_post_comment_with_id(post_comment_id=post_comment_id) + post_comment = user.mute_post_comment_with_id(post_comment_id=post_comment_id) - return ApiMessageResponse(message=_('Post comment muted.'), status=status.HTTP_200_OK) + post_comment_serializer = MuteUnmutePostCommentResponseSerializer(post_comment, context={"request": request}) + + return Response(post_comment_serializer.data, status=status.HTTP_200_OK) class UnmutePostComment(APIView): @@ -118,9 +120,11 @@ def post(self, request, post_uuid, post_comment_id): user = request.user with transaction.atomic(): - user.unmute_post_comment_with_id(post_comment_id=post_comment_id) + post_comment = user.unmute_post_comment_with_id(post_comment_id=post_comment_id) - return ApiMessageResponse(message=_('Post comment unmuted.'), status=status.HTTP_200_OK) + post_comment_serializer = MuteUnmutePostCommentResponseSerializer(post_comment, context={"request": request}) + + return Response(post_comment_serializer.data, status=status.HTTP_200_OK) class PostCommentNotificationsSubscriptionSettings(APIView): diff --git a/openbook_posts/views/post_comments/serializers.py b/openbook_posts/views/post_comments/serializers.py index de09b649..ee639d00 100644 --- a/openbook_posts/views/post_comments/serializers.py +++ b/openbook_posts/views/post_comments/serializers.py @@ -5,7 +5,8 @@ from openbook_common.models import Emoji, Language from openbook_common.serializers import CommonUserProfileBadgeSerializer, CommonHashtagSerializer from openbook_common.serializers_fields.post_comment import PostCommenterField, RepliesCountField, \ - PostCommentReactionsEmojiCountField, PostCommentReactionField, PostCommentIsMutedField + PostCommentReactionsEmojiCountField, PostCommentReactionField, PostCommentIsMutedField, \ + CommonPostCommentNotificationsSubscriptionField from openbook_communities.models import CommunityMembership from openbook_posts.models import PostComment, Post, PostCommentReaction from openbook_posts.validators import post_uuid_exists, post_comment_text_validators @@ -106,6 +107,7 @@ class PostCommentReplySerializer(serializers.ModelSerializer): is_muted = PostCommentIsMutedField() language = PostCommentLanguageSerializer() hashtags = CommonHashtagSerializer(many=True) + post_comment_notifications_subscription = CommonPostCommentNotificationsSubscriptionField() class Meta: model = PostComment @@ -120,7 +122,8 @@ class Meta: 'reactions_emoji_counts', 'id', 'reaction', - 'hashtags' + 'hashtags', + 'post_comment_notifications_subscription' ) @@ -135,6 +138,7 @@ class PostCommentSerializer(serializers.ModelSerializer): is_muted = PostCommentIsMutedField() language = PostCommentLanguageSerializer() hashtags = CommonHashtagSerializer(many=True) + post_comment_notifications_subscription = CommonPostCommentNotificationsSubscriptionField() class Meta: model = PostComment @@ -150,7 +154,8 @@ class Meta: 'is_edited', 'is_muted', 'hashtags', - 'id' + 'id', + 'post_comment_notifications_subscription' ) From 8cc2a0659fdea6e682f338e754f0d4fa6bfacc4b Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 22 Apr 2020 12:33:57 +0200 Subject: [PATCH 38/40] :sparkles: use one toggle for post_notifications --- .../migrations/0054_auto_20200422_1232.py | 42 ++++++++++++ openbook_auth/models.py | 65 +++++-------------- 2 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 openbook_auth/migrations/0054_auto_20200422_1232.py diff --git a/openbook_auth/migrations/0054_auto_20200422_1232.py b/openbook_auth/migrations/0054_auto_20200422_1232.py new file mode 100644 index 00000000..3f46a51e --- /dev/null +++ b/openbook_auth/migrations/0054_auto_20200422_1232.py @@ -0,0 +1,42 @@ +# Generated by Django 2.2.12 on 2020-04-22 10:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('openbook_auth', '0053_remove_usernotificationssettings_post_subscription_comment_notifications'), + ] + + operations = [ + migrations.RemoveField( + model_name='usernotificationssettings', + name='post_comment_notifications', + ), + migrations.RemoveField( + model_name='usernotificationssettings', + name='post_comment_reaction_notifications', + ), + migrations.RemoveField( + model_name='usernotificationssettings', + name='post_comment_reply_notifications', + ), + migrations.RemoveField( + model_name='usernotificationssettings', + name='post_comment_user_mention_notifications', + ), + migrations.RemoveField( + model_name='usernotificationssettings', + name='post_reaction_notifications', + ), + migrations.RemoveField( + model_name='usernotificationssettings', + name='post_user_mention_notifications', + ), + migrations.AddField( + model_name='usernotificationssettings', + name='post_notifications', + field=models.BooleanField(default=True, verbose_name='post notifications'), + ), + ] diff --git a/openbook_auth/models.py b/openbook_auth/models.py index 8e145aef..9b6aa138 100644 --- a/openbook_auth/models.py +++ b/openbook_auth/models.py @@ -523,33 +523,25 @@ def update(self, profile.save() self.save() - def update_notifications_settings(self, post_comment_notifications=None, post_reaction_notifications=None, - follow_notifications=None, connection_request_notifications=None, + def update_notifications_settings(self, + follow_notifications=None, + connection_request_notifications=None, connection_confirmed_notifications=None, community_invite_notifications=None, community_new_post_notifications=None, user_new_post_notifications=None, - post_comment_reaction_notifications=None, - post_comment_reply_notifications=None, - post_comment_user_mention_notifications=None, - post_user_mention_notifications=None, - ): + post_notifications=None): notifications_settings = self.notifications_settings notifications_settings.update( - post_comment_notifications=post_comment_notifications, - post_reaction_notifications=post_reaction_notifications, follow_notifications=follow_notifications, connection_request_notifications=connection_request_notifications, connection_confirmed_notifications=connection_confirmed_notifications, community_invite_notifications=community_invite_notifications, community_new_post_notifications=community_new_post_notifications, user_new_post_notifications=user_new_post_notifications, - post_comment_reaction_notifications=post_comment_reaction_notifications, - post_comment_reply_notifications=post_comment_reply_notifications, - post_comment_user_mention_notifications=post_comment_user_mention_notifications, - post_user_mention_notifications=post_user_mention_notifications + post_notifications=post_notifications ) return notifications_settings @@ -805,29 +797,29 @@ def has_follow_notifications_enabled(self): return self.notifications_settings.follow_notifications def has_post_comment_mention_notifications_enabled(self): - return self.notifications_settings.post_comment_user_mention_notifications + return self.notifications_settings.post_notifications def has_post_mention_notifications_enabled(self): - return self.notifications_settings.post_user_mention_notifications + return self.notifications_settings.post_notifications def has_reaction_notifications_enabled_for_post_with_id(self, post_id): - return self.notifications_settings.post_reaction_notifications and \ + return self.notifications_settings.post_notifications and \ not self.has_muted_post_with_id(post_id=post_id) and \ not self.has_disabled_reaction_notifications_for_post_with_id(post_id=post_id) def has_reaction_notifications_enabled_for_post_comment(self, post_comment): - return self.notifications_settings.post_comment_reaction_notifications and \ + return self.notifications_settings.post_notifications and \ not self.has_muted_post_with_id(post_id=post_comment.post_id) and \ not self.has_muted_post_comment_with_id(post_comment_id=post_comment.id) and \ not self.has_disabled_reaction_notifications_for_post_comment(post_comment=post_comment) def has_comment_notifications_enabled_for_post_with_id(self, post_id): - return self.notifications_settings.post_comment_notifications and \ + return self.notifications_settings.post_notifications and \ not self.has_muted_post_with_id(post_id=post_id) and \ not self.has_disabled_comment_notifications_for_post(post_id=post_id) def has_reply_notifications_enabled_for_post_comment(self, post_comment): - return self.notifications_settings.post_comment_reply_notifications and \ + return self.notifications_settings.post_notifications and \ not self.has_muted_post_with_id(post_id=post_comment.post_id) and \ not self.has_muted_post_comment_with_id(post_comment_id=post_comment.id) and \ not self.has_disabled_reply_notifications_for_post_comment(post_comment_id=post_comment.id) @@ -3827,54 +3819,29 @@ def __str__(self): class UserNotificationsSettings(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='notifications_settings') - post_comment_notifications = models.BooleanField(_('post comment notifications'), default=True) - post_comment_reply_notifications = models.BooleanField(_('post comment reply notifications'), default=True) - post_reaction_notifications = models.BooleanField(_('post reaction notifications'), default=True) follow_notifications = models.BooleanField(_('follow notifications'), default=True) connection_request_notifications = models.BooleanField(_('connection request notifications'), default=True) connection_confirmed_notifications = models.BooleanField(_('connection confirmed notifications'), default=True) community_invite_notifications = models.BooleanField(_('community invite notifications'), default=True) community_new_post_notifications = models.BooleanField(_('community new post notifications'), default=True) user_new_post_notifications = models.BooleanField(_('user new post notifications'), default=True) - post_comment_reaction_notifications = models.BooleanField(_('post comment reaction notifications'), default=True) - post_comment_user_mention_notifications = models.BooleanField(_('post comment user mention notifications'), - default=True) - post_user_mention_notifications = models.BooleanField(_('post user mention notifications'), default=True) + post_notifications = models.BooleanField(_('post notifications'), default=True) @classmethod def create_notifications_settings(cls, user): return UserNotificationsSettings.objects.create(user=user) - def update(self, post_comment_notifications=None, - post_comment_reply_notifications=None, - post_reaction_notifications=None, + def update(self, follow_notifications=None, connection_request_notifications=None, connection_confirmed_notifications=None, community_invite_notifications=None, community_new_post_notifications=None, user_new_post_notifications=None, - post_comment_user_mention_notifications=None, - post_user_mention_notifications=None, - post_comment_reaction_notifications=None): - - if post_comment_notifications is not None: - self.post_comment_notifications = post_comment_notifications - - if post_comment_user_mention_notifications is not None: - self.post_comment_user_mention_notifications = post_comment_user_mention_notifications - - if post_user_mention_notifications is not None: - self.post_user_mention_notifications = post_user_mention_notifications - - if post_comment_reaction_notifications is not None: - self.post_comment_reaction_notifications = post_comment_reaction_notifications - - if post_comment_reply_notifications is not None: - self.post_comment_reply_notifications = post_comment_reply_notifications + post_notifications=None): - if post_reaction_notifications is not None: - self.post_reaction_notifications = post_reaction_notifications + if post_notifications is not None: + self.post_notifications = post_notifications if follow_notifications is not None: self.follow_notifications = follow_notifications From 060d22adf3c41dd347c07d66e3ad399af3f6ebde Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 22 Apr 2020 12:34:21 +0200 Subject: [PATCH 39/40] :recycle: remove code related to other toggles, modify tests --- .../tests/views/test_authenticated_user.py | 28 +++---------------- openbook_auth/views/auth/serializers.py | 14 ++-------- .../views/authenticated_user/views.py | 14 ++-------- 3 files changed, 8 insertions(+), 48 deletions(-) diff --git a/openbook_auth/tests/views/test_authenticated_user.py b/openbook_auth/tests/views/test_authenticated_user.py index fc6d5eab..edbb85ec 100644 --- a/openbook_auth/tests/views/test_authenticated_user.py +++ b/openbook_auth/tests/views/test_authenticated_user.py @@ -589,49 +589,34 @@ def test_can_update_notifications_settings(self): user = make_user() notifications_settings = user.notifications_settings - notifications_settings.post_comment_notifications = fake.boolean() - notifications_settings.post_reaction_notifications = fake.boolean() notifications_settings.follow_notifications = fake.boolean() notifications_settings.connection_request_notifications = fake.boolean() notifications_settings.connection_confirmed_notifications = fake.boolean() notifications_settings.community_invite_notifications = fake.boolean() notifications_settings.community_new_post_notifications = fake.boolean() notifications_settings.user_new_post_notifications = fake.boolean() - notifications_settings.post_comment_reply_notifications = fake.boolean() - notifications_settings.post_comment_reaction_notifications = fake.boolean() - notifications_settings.post_comment_user_mention_notifications = fake.boolean() - notifications_settings.post_user_mention_notifications = fake.boolean() + notifications_settings.post_notifications = fake.boolean() notifications_settings.save() headers = make_authentication_headers_for_user(user) - new_post_comment_notifications = not notifications_settings.post_comment_notifications - new_post_reaction_notifications = not notifications_settings.post_reaction_notifications new_follow_notifications = not notifications_settings.follow_notifications new_connection_request_notifications = not notifications_settings.connection_request_notifications new_connection_confirmed_notifications = not notifications_settings.connection_confirmed_notifications new_community_invite_notifications = not notifications_settings.community_invite_notifications new_community_new_post_notifications = not notifications_settings.community_new_post_notifications new_user_new_post_notifications = not notifications_settings.user_new_post_notifications - new_post_comment_reaction_notifications = not notifications_settings.post_comment_reaction_notifications - new_post_comment_reply_notifications = not notifications_settings.post_comment_reply_notifications - new_post_comment_user_mention_notifications = not notifications_settings.post_comment_user_mention_notifications - new_post_user_mention_notifications = not notifications_settings.post_user_mention_notifications + new_post_notifications = not notifications_settings.post_notifications data = { - 'post_comment_notifications': new_post_comment_notifications, - 'post_reaction_notifications': new_post_reaction_notifications, 'follow_notifications': new_follow_notifications, 'connection_request_notifications': new_connection_request_notifications, 'connection_confirmed_notifications': new_connection_confirmed_notifications, 'community_invite_notifications': new_community_invite_notifications, 'community_new_post_notifications': new_community_new_post_notifications, 'user_new_post_notifications': new_user_new_post_notifications, - 'post_comment_reply_notifications': new_post_comment_reply_notifications, - 'post_comment_reaction_notifications': new_post_comment_reaction_notifications, - 'post_comment_user_mention_notifications': new_post_comment_user_mention_notifications, - 'post_user_mention_notifications': new_post_user_mention_notifications, + 'post_notifications': new_post_notifications, } url = self._get_url() @@ -642,8 +627,6 @@ def test_can_update_notifications_settings(self): notifications_settings.refresh_from_db() - self.assertEqual(notifications_settings.post_comment_notifications, new_post_comment_notifications) - self.assertEqual(notifications_settings.post_reaction_notifications, new_post_reaction_notifications) self.assertEqual(notifications_settings.follow_notifications, new_follow_notifications) self.assertEqual(notifications_settings.connection_request_notifications, new_connection_request_notifications) self.assertEqual(notifications_settings.community_invite_notifications, new_community_invite_notifications) @@ -651,10 +634,7 @@ def test_can_update_notifications_settings(self): self.assertEqual(notifications_settings.user_new_post_notifications, new_user_new_post_notifications) self.assertEqual(notifications_settings.connection_confirmed_notifications, new_connection_confirmed_notifications) - self.assertEqual(notifications_settings.post_comment_reply_notifications, - new_post_comment_reply_notifications) - self.assertEqual(notifications_settings.post_comment_reaction_notifications, - new_post_comment_reaction_notifications) + self.assertEqual(notifications_settings.post_notifications, new_post_notifications) def _get_url(self): return reverse('authenticated-user-notifications-settings') diff --git a/openbook_auth/views/auth/serializers.py b/openbook_auth/views/auth/serializers.py index 5bdb2f60..c9b94959 100644 --- a/openbook_auth/views/auth/serializers.py +++ b/openbook_auth/views/auth/serializers.py @@ -58,34 +58,24 @@ class Meta: model = UserNotificationsSettings fields = ( 'id', - 'post_comment_notifications', - 'post_reaction_notifications', 'follow_notifications', 'connection_request_notifications', 'connection_confirmed_notifications', 'community_invite_notifications', 'community_new_post_notifications', 'user_new_post_notifications', - 'post_comment_reaction_notifications', - 'post_comment_reply_notifications', - 'post_comment_user_mention_notifications', - 'post_user_mention_notifications' + 'post_notifications' ) class UpdateAuthenticatedUserNotificationsSettingsSerializer(serializers.Serializer): - post_comment_notifications = serializers.BooleanField(required=False) - post_reaction_notifications = serializers.BooleanField(required=False) follow_notifications = serializers.BooleanField(required=False) connection_request_notifications = serializers.BooleanField(required=False) connection_confirmed_notifications = serializers.BooleanField(required=False) community_invite_notifications = serializers.BooleanField(required=False) community_new_post_notifications = serializers.BooleanField(required=False) user_new_post_notifications = serializers.BooleanField(required=False) - post_comment_reaction_notifications = serializers.BooleanField(required=False) - post_comment_reply_notifications = serializers.BooleanField(required=False) - post_comment_user_mention_notifications = serializers.BooleanField(required=False) - post_user_mention_notifications = serializers.BooleanField(required=False) + post_notifications = serializers.BooleanField(required=False) class RequestPasswordResetSerializer(serializers.Serializer): diff --git a/openbook_auth/views/authenticated_user/views.py b/openbook_auth/views/authenticated_user/views.py index 585f1598..ee02b7f4 100644 --- a/openbook_auth/views/authenticated_user/views.py +++ b/openbook_auth/views/authenticated_user/views.py @@ -100,35 +100,25 @@ def patch(self, request): serializer.is_valid(raise_exception=True) data = serializer.validated_data - post_comment_notifications = data.get('post_comment_notifications') - post_reaction_notifications = data.get('post_reaction_notifications') follow_notifications = data.get('follow_notifications') connection_request_notifications = data.get('connection_request_notifications') connection_confirmed_notifications = data.get('connection_confirmed_notifications') community_invite_notifications = data.get('community_invite_notifications') community_new_post_notifications = data.get('community_new_post_notifications') user_new_post_notifications = data.get('user_new_post_notifications') - post_comment_reaction_notifications = data.get('post_comment_reaction_notifications') - post_comment_reply_notifications = data.get('post_comment_reply_notifications') - post_comment_user_mention_notifications = data.get('post_comment_user_mention_notifications') - post_user_mention_notifications = data.get('post_user_mention_notifications') + post_notifications = data.get('post_notifications') user = request.user with transaction.atomic(): notifications_settings = user.update_notifications_settings( - post_comment_notifications=post_comment_notifications, - post_reaction_notifications=post_reaction_notifications, follow_notifications=follow_notifications, connection_request_notifications=connection_request_notifications, connection_confirmed_notifications=connection_confirmed_notifications, community_invite_notifications=community_invite_notifications, community_new_post_notifications=community_new_post_notifications, user_new_post_notifications=user_new_post_notifications, - post_comment_reaction_notifications=post_comment_reaction_notifications, - post_comment_reply_notifications=post_comment_reply_notifications, - post_comment_user_mention_notifications=post_comment_user_mention_notifications, - post_user_mention_notifications=post_user_mention_notifications + post_notifications=post_notifications ) user_notifications_settings_serializer = AuthenticatedUserNotificationsSettingsSerializer( From 98458f92168fe56e2ff4d176baaca24f28e95afe Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 22 Apr 2020 16:20:11 +0200 Subject: [PATCH 40/40] :white_check_mark: add comment notification sub tests --- openbook_posts/models.py | 8 +- openbook_posts/tests/views/test_post.py | 99 ++- .../tests/views/test_post_comment.py | 569 +++++++++++++++++- 3 files changed, 648 insertions(+), 28 deletions(-) diff --git a/openbook_posts/models.py b/openbook_posts/models.py index d0c08bc1..780a3f27 100644 --- a/openbook_posts/models.py +++ b/openbook_posts/models.py @@ -257,11 +257,11 @@ def get_post_comment_notification_target_users(cls, post, post_commenter): other_commenters_query = Q(posts_comments__post_id=post.pk, posts_comments__parent_comment_id=None,) other_target_users = User.objects. \ - only('id', 'username', 'notifications_settings__post_comment_notifications'). \ + only('id', 'username', 'notifications_settings__post_notifications'). \ filter((post_subscribers_query | other_commenters_query) & ~Q(id=post_commenter.pk)) post_creator = User.objects. \ - only('id', 'username', 'notifications_settings__post_comment_notifications'). \ + only('id', 'username', 'notifications_settings__post_notifications'). \ filter(pk=post.creator_id) return other_target_users.union(post_creator) @@ -282,11 +282,11 @@ def get_post_comment_reply_notification_target_users(cls, post, post_comment): post_comment_reply_subscribers_query = Q(post_comment_notifications_subscriptions__post_comment_id=post_comment.pk) post_target_users = User.objects.\ - only('id', 'username', 'notifications_settings__post_comment_notifications').\ + only('id', 'username', 'notifications_settings__post_notifications').\ filter(post_subscribers_query) post_comment_reply_target_users = User.objects.\ - only('id', 'username', 'notifications_settings__post_comment_notifications').\ + only('id', 'username', 'notifications_settings__post_notifications').\ filter(post_comment_reply_subscribers_query) return post_target_users.union(post_comment_reply_target_users) diff --git a/openbook_posts/tests/views/test_post.py b/openbook_posts/tests/views/test_post.py index 822fd673..88c2b259 100644 --- a/openbook_posts/tests/views/test_post.py +++ b/openbook_posts/tests/views/test_post.py @@ -1329,7 +1329,10 @@ def test_can_subscribe_comment_notifications_for_public_post(self): response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertTrue(subscriber.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=subscriber, + comment_notifications=True).exists()) def test_cannot_subscribe_to_comment_notifications_for_post_if_encircled_post(self): """ @@ -1351,8 +1354,10 @@ def test_cannot_subscribe_to_comment_notifications_for_post_if_encircled_post(se response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_subscribe_to_comment_notifications_for_post_if_part_of_encircled_post(self): """ @@ -1378,8 +1383,10 @@ def test_can_subscribe_to_comment_notifications_for_post_if_part_of_encircled_po response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_subscribe_to_comment_notifications_for_community_post_if_public(self): """ @@ -1402,8 +1409,10 @@ def test_can_subscribe_to_comment_notifications_for_community_post_if_public(sel response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_cannot_subscribe_to_comment_notifications_for_closed_community_post_if_public(self): """ @@ -1428,8 +1437,10 @@ def test_cannot_subscribe_to_comment_notifications_for_closed_community_post_if_ response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_creator(self): """ @@ -1454,7 +1465,10 @@ def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_cre response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_subscribe_to_comment_notifications_for_closed_community_post_administrator(self): """ @@ -1479,7 +1493,10 @@ def test_can_subscribe_to_comment_notifications_for_closed_community_post_admini response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertTrue(admin.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=admin, + comment_notifications=True).exists()) def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_moderator(self): """ @@ -1507,7 +1524,10 @@ def test_can_subscribe_to_comment_notifications_for_closed_community_post_if_mod response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertTrue(moderator.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=moderator, + comment_notifications=True).exists()) def test_cannot_subscribe_to_comment_notifications_for_community_post_if_private_and_not_member(self): """ @@ -1529,7 +1549,10 @@ def test_cannot_subscribe_to_comment_notifications_for_community_post_if_private self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_subscribe_to_comment_notifications_for_community_post_if_private_and_member(self): """ @@ -1555,7 +1578,10 @@ def test_can_subscribe_to_comment_notifications_for_community_post_if_private_an response = self.client.put(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_unsubscribe_comment_notifications_for_public_post(self): """ @@ -1600,7 +1626,10 @@ def test_cannot_unsubscribe_to_comment_notifications_for_post_if_encircled_post( response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_unsubscribe_to_comment_notifications_for_post_if_part_of_encircled_post(self): """ @@ -1625,7 +1654,10 @@ def test_can_unsubscribe_to_comment_notifications_for_post_if_part_of_encircled_ response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_unsubscribe_to_comment_notifications_for_community_post_if_public(self): """ @@ -1648,7 +1680,10 @@ def test_can_unsubscribe_to_comment_notifications_for_community_post_if_public(s response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_cannot_unsubscribe_to_comment_notifications_for_closed_community_post_if_public(self): """ @@ -1674,7 +1709,10 @@ def test_cannot_unsubscribe_to_comment_notifications_for_closed_community_post_i response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertTrue(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertTrue(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_creator(self): """ @@ -1699,7 +1737,10 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_c response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_administrator(self): """ @@ -1724,7 +1765,10 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_admi response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(admin.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=admin, + comment_notifications=True).exists()) def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_moderator(self): """ @@ -1753,7 +1797,10 @@ def test_can_unsubscribe_to_comment_notifications_for_closed_community_post_if_m response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(moderator.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=moderator, + comment_notifications=True).exists()) def test_cannot_unsubscribe_to_comment_notifications_for_community_post_if_private_and_not_member(self): """ @@ -1775,7 +1822,10 @@ def test_cannot_unsubscribe_to_comment_notifications_for_community_post_if_priva self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def test_can_unsubscribe_to_comment_notifications_for_community_post_if_private_and_member(self): """ @@ -1802,7 +1852,10 @@ def test_can_unsubscribe_to_comment_notifications_for_community_post_if_private_ response = self.client.patch(url, data, **headers) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertFalse(user.are_post_subscription_comment_notifications_enabled_for_post(post=post)) + PostNotificationsSubscription = get_post_notifications_subscription_model() + self.assertFalse(PostNotificationsSubscription.objects.filter(post=post, + subscriber=user, + comment_notifications=True).exists()) def _get_url(self, post): return reverse('post-notifications-subscription-settings', kwargs={ diff --git a/openbook_posts/tests/views/test_post_comment.py b/openbook_posts/tests/views/test_post_comment.py index 2e1ea964..efe0c7c1 100644 --- a/openbook_posts/tests/views/test_post_comment.py +++ b/openbook_posts/tests/views/test_post_comment.py @@ -9,7 +9,7 @@ from openbook_common.tests.helpers import make_authentication_headers_for_user, make_fake_post_text, \ make_fake_post_comment_text, make_user, make_circle, make_community, make_hashtag_name, make_hashtag -from openbook_common.utils.model_loaders import get_language_model +from openbook_common.utils.model_loaders import get_language_model, get_post_comment_notifications_subscription_model from openbook_communities.models import Community from openbook_hashtags.models import Hashtag from openbook_notifications.models import PostCommentNotification, Notification, PostCommentReplyNotification, \ @@ -2755,3 +2755,570 @@ def _get_url(self, post, post_comment): 'post_uuid': post.uuid, 'post_comment_id': post_comment.id, }) + + +class PostCommentNotificationsSubscriptionSettingsAPITests(OpenbookAPITestCase): + """ + PostCommentNotificationsSubscriptionSettings API + """ + + fixtures = [ + 'openbook_circles/fixtures/circles.json' + ] + + def test_can_subscribe_reply_notifications_for_public_post_comment(self): + """ + should be able to subscribe to comment notifications for public post comment and return 200 + """ + user = make_user() + commenter = make_user() + subscriber = make_user() + headers = make_authentication_headers_for_user(subscriber) + post = user.create_public_post(text=make_fake_post_text()) + post_comment = commenter.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + data = { + 'reply_notifications': True + } + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertFalse(subscriber.has_disabled_reply_notifications_for_post_comment(post_comment_id=post_comment.id)) + + def test_cannot_subscribe_to_comment_notifications_for_post_comment_if_encircled_post(self): + """ + should NOT be able to subscribe to reply notifications for comment if encircled post + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + data = { + 'reply_notifications': True + } + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertFalse(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + post_comment_id=post_comment.id).exists()) + + def test_can_subscribe_to_reply_notifications_for_post_comment_if_part_of_encircled_post(self): + """ + should be able to subscribe to reply notifications for comment if part of encircled post + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) + user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) + + data = { + 'reply_notifications': True + } + + url = self._get_url(post, post_comment) + + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertTrue(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + reply_notifications=True, + post_comment_id=post_comment.id).exists()) + + def test_can_subscribe_to_reply_notifications_for_community_post_if_public(self): + """ + should be able to subscribe to reply notifications for community post comment if public + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + + headers = make_authentication_headers_for_user(user) + user.join_community_with_name(community_name=community.name) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + data = { + 'reply_notifications': True + } + url = self._get_url(post, post_comment) + + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertTrue(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + reply_notifications=True, + post_comment_id=post_comment.id).exists()) + + def test_cannot_subscribe_to_reply_notifications_for_closed_community_post_if_public(self): + """ + should NOT be able to subscribe to reply notifications for closed community post + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': True + } + + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertFalse(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + post_comment_id=post_comment.id).exists()) + + def test_can_subscribe_to_reply_notifications_for_closed_community_post_if_creator(self): + """ + should be able to subscribe to reply notifications for closed post if post creator in community + """ + user = make_user() + commenter = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + commenter.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = commenter.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': True + } + + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertTrue(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + reply_notifications=True, + post_comment_id=post_comment.id).exists()) + + def test_can_subscribe_to_reply_notifications_for_closed_community_post_administrator(self): + """ + should be able to subscribe to reply notifications for closed post if administrator in community + """ + user = make_user() + + admin = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(admin) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': True + } + + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertTrue(PostCommentNotificationsSubscription.objects.filter(subscriber=admin, + reply_notifications=True, + post_comment_id=post_comment.id).exists()) + + def test_can_subscribe_to_reply_notifications_for_closed_community_post_if_moderator(self): + """ + should be able to subscribe to reply notifications for closed post if moderator in community + """ + user = make_user() + + admin = make_user() + moderator = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + moderator.join_community_with_name(community_name=community.name) + admin.add_moderator_with_username_to_community_with_name(username=moderator.username, + community_name=community.name) + + headers = make_authentication_headers_for_user(moderator) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': True + } + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertTrue(PostCommentNotificationsSubscription.objects.filter(subscriber=moderator, + reply_notifications=True, + post_comment_id=post_comment.id).exists()) + + def test_cannot_subscribe_to_reply_notifications_for_community_post_if_private_and_not_member(self): + """ + should NOT be able to subscribe to reply notifications for private community post and not a member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + data = { + 'reply_notifications': True + } + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertFalse(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + post_comment_id=post_comment.id).exists()) + + def test_can_subscribe_to_reply_notifications_for_community_post_if_private_and_member(self): + """ + should be able to subscribe to reply notifications for private community post if member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + foreign_user.invite_user_with_username_to_community_with_name(username=user.username, + community_name=community.name) + + user.join_community_with_name(community_name=community.name) + + data = { + 'reply_notifications': True + } + url = self._get_url(post, post_comment) + response = self.client.put(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertTrue(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + reply_notifications=True, + post_comment_id=post_comment.id).exists()) + + def test_can_unsubscribe_reply_notifications_for_public_post(self): + """ + should be able to unsubscribe to reply notifications for public post and return 200 + """ + user = make_user() + subscriber = make_user() + headers = make_authentication_headers_for_user(subscriber) + post = user.create_public_post(text=make_fake_post_text()) + post_comment = user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + post_comment_notifications_subscription = \ + subscriber.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + post_comment_notifications_subscription.refresh_from_db() + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(post_comment_notifications_subscription.reply_notifications) + + def test_cannot_unsubscribe_to_reply_notifications_for_post_comment_if_encircled_post(self): + """ + should NOT be able to unsubscribe to reply notifications for comment if encircled post and not part of circle + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + PostCommentNotificationsSubscription = get_post_comment_notifications_subscription_model() + self.assertFalse(PostCommentNotificationsSubscription.objects.filter(subscriber=user, + post_comment_id=post_comment.id).exists()) + + def test_can_unsubscribe_to_reply_notifications_for_post_comment_if_part_of_encircled_post(self): + """ + should be able to unsubscribe to reply notifications for comment if part of encircled post + """ + user = make_user() + foreign_user = make_user() + + headers = make_authentication_headers_for_user(user) + + circle = make_circle(creator=foreign_user) + + post = foreign_user.create_encircled_post(text=make_fake_post_text(), circles_ids=[circle.pk]) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + foreign_user.connect_with_user_with_id(user_id=user.pk, circles_ids=[circle.pk]) + user.confirm_connection_with_user_with_id(user_id=foreign_user.pk) + post_comment_notifications_subscription = \ + user.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + post_comment_notifications_subscription.refresh_from_db() + self.assertFalse(post_comment_notifications_subscription.reply_notifications) + + def test_can_unsubscribe_to_reply_notifications_for_community_post_comment_if_public(self): + """ + should be able to unsubscribe to reply notifications for community post comment if public + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + + headers = make_authentication_headers_for_user(user) + user.join_community_with_name(community_name=community.name) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post_comment_notifications_subscription = \ + user.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + post_comment_notifications_subscription.refresh_from_db() + self.assertFalse(post_comment_notifications_subscription.reply_notifications) + + def test_cannot_unsubscribe_to_reply_notifications_for_closed_community_post_if_public(self): + """ + should NOT be able to unsubscribe to reply notifications on comments for closed community post + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post_comment_notifications_subscription = \ + user.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + post_comment_notifications_subscription.refresh_from_db() + self.assertTrue(post_comment_notifications_subscription.reply_notifications) + + def test_can_unsubscribe_to_reply_notifications_on_comment_for_closed_community_post_if_creator(self): + """ + should be able to unsubscribe to reply notifications on comment for closed post if post creator in community + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(user) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post_comment_notifications_subscription = \ + user.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + post_comment_notifications_subscription.refresh_from_db() + self.assertFalse(post_comment_notifications_subscription.reply_notifications) + + def test_can_unsubscribe_to_reply_notifications_for_comment_in_closed_community_post_administrator(self): + """ + should be able to unsubscribe to reply notifications on comment for closed post if administrator in community + """ + user = make_user() + + admin = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + + headers = make_authentication_headers_for_user(admin) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + post_comment_notifications_subscription = \ + admin.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + post_comment_notifications_subscription.refresh_from_db() + self.assertFalse(post_comment_notifications_subscription.reply_notifications) + + def test_can_unsubscribe_to_reply_notifications_for_closed_community_post_if_moderator(self): + """ + should be able to unsubscribe to reply notifications on comment for closed post if moderator in community + """ + user = make_user() + + admin = make_user() + moderator = make_user() + community = make_community(creator=admin) + user.join_community_with_name(community_name=community.name) + moderator.join_community_with_name(community_name=community.name) + admin.add_moderator_with_username_to_community_with_name(username=moderator.username, + community_name=community.name) + + headers = make_authentication_headers_for_user(moderator) + post = user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + post_comment_notifications_subscription = \ + moderator.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + post.is_closed = True + post.save() + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + post_comment_notifications_subscription.refresh_from_db() + self.assertFalse(post_comment_notifications_subscription.reply_notifications) + + def test_cannot_unsubscribe_to_reply_notifications_for_comment_on_community_post_if_private_and_not_member(self): + """ + should NOT be able to unsubscribe to reply notifications on comment for private community post if not a member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_can_unsubscribe_to_reply_notifications_for_comment_on_community_post_if_private_and_member(self): + """ + should be able to unsubscribe to reply notifications on comment for private community post if member + """ + user = make_user() + + foreign_user = make_user() + community = make_community(creator=foreign_user, type='T') + + headers = make_authentication_headers_for_user(user) + post = foreign_user.create_community_post(text=make_fake_post_text(), community_name=community.name) + post_comment = foreign_user.comment_post_with_id(post_id=post.id, text=make_fake_post_comment_text()) + + foreign_user.invite_user_with_username_to_community_with_name(username=user.username, + community_name=community.name) + + user.join_community_with_name(community_name=community.name) + post_comment_notifications_subscription = \ + user.create_post_comment_notifications_subscription_for_comment_with_id(post_comment_id=post_comment.id, + reply_notifications=True) + data = { + 'reply_notifications': False + } + url = self._get_url(post, post_comment) + response = self.client.patch(url, data, **headers) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + post_comment_notifications_subscription.refresh_from_db() + self.assertFalse(post_comment_notifications_subscription.reply_notifications) + + def _get_url(self, post, post_comment): + return reverse('post-comment-notifications-subscription-settings', kwargs={ + 'post_uuid': post.uuid, + 'post_comment_id': post_comment.id + })