Skip to content

Commit

Permalink
Merge pull request #2128 from ResearchHub/feed-comment-signals
Browse files Browse the repository at this point in the history
feat: Add comment signal handlers for feed
  • Loading branch information
gzurowski authored Feb 28, 2025
2 parents 4f4cd34 + b2aee45 commit b1ceb29
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/feed/signals/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .bounty_signals import * # noqa: F401, F403
from .comment_signals import * # noqa: F401, F403
from .paper_signals import * # noqa: F401, F403
from .post_signals import * # noqa: F401, F403
74 changes: 74 additions & 0 deletions src/feed/signals/comment_signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver

from feed.models import FeedEntry
from feed.tasks import create_feed_entry, delete_feed_entry
from researchhub_comment.related_models.rh_comment_model import RhCommentModel

"""
Signal handlers for Comment model.
The signal handlers are responsbile for creating and deleting feed entries
when comments are created and removed, respectively.
"""


@receiver(post_save, sender=RhCommentModel)
def handle_comment_created_or_removed(sender, instance, created, **kwargs):
"""
When a comment is created or removed, create or delete a feed entry.
"""
comment = instance

if created:
_handle_comment_created(comment)
elif comment.is_removed:
_handle_comment_removed(comment)


def _handle_comment_created(comment):
# Validate that the comment is associated with a unified document with hubs
if not getattr(comment, "unified_document", None) or not hasattr(
comment.unified_document, "hubs"
):
return

tasks = [
create_feed_entry.apply_async(
args=(
comment.id,
ContentType.objects.get_for_model(comment).id,
FeedEntry.PUBLISH,
hub.id,
ContentType.objects.get_for_model(hub).id,
comment.created_by.id,
),
priority=1,
)
for hub in comment.unified_document.hubs.all()
]
transaction.on_commit(lambda: [task() for task in tasks])


def _handle_comment_removed(comment):
# Validate that the comment is associated with a unified document with hubs
if not getattr(comment, "unified_document", None) or not hasattr(
comment.unified_document, "hubs"
):
return

tasks = [
delete_feed_entry.apply_async(
args=(
comment.id,
ContentType.objects.get_for_model(comment).id,
hub.id,
ContentType.objects.get_for_model(hub).id,
),
priority=1,
)
for hub in comment.unified_document.hubs.all()
]
transaction.on_commit(lambda: [task() for task in tasks])
1 change: 0 additions & 1 deletion src/feed/tests/signals/test_bounty_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
handle_bounty_create_feed_entry,
)
from hub.models import Hub
from paper.related_models.paper_model import Paper
from paper.tests.helpers import create_paper
from reputation.related_models.bounty import Bounty
from reputation.related_models.escrow import Escrow
Expand Down
134 changes: 134 additions & 0 deletions src/feed/tests/signals/test_comment_signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from unittest.mock import MagicMock, call, patch

from django.contrib.contenttypes.models import ContentType
from django.test import TestCase

from feed.models import FeedEntry
from feed.tests.test_views import User
from hub.models import Hub
from paper.related_models.paper_model import Paper
from researchhub_comment.constants.rh_comment_thread_types import GENERIC_COMMENT
from researchhub_comment.related_models.rh_comment_model import RhCommentModel
from researchhub_comment.related_models.rh_comment_thread_model import (
RhCommentThreadModel,
)
from researchhub_document.related_models.constants import document_type
from researchhub_document.related_models.researchhub_unified_document_model import (
ResearchhubUnifiedDocument,
)


class CommentSignalsTests(TestCase):

def setUp(self):
self.user = User.objects.create_user(username="user1")
self.hub1 = Hub.objects.create(name="hub1")
self.hub2 = Hub.objects.create(name="hub2")
self.unified_document = ResearchhubUnifiedDocument.objects.create(
document_type=document_type.DISCUSSION,
)
self.unified_document.hubs.add(self.hub1)
self.unified_document.hubs.add(self.hub2)
self.paper = Paper.objects.create(
title="paper1", unified_document=self.unified_document
)

self.content_type = ContentType.objects.get_for_model(Paper)
self.thread = RhCommentThreadModel.objects.create(
thread_type=GENERIC_COMMENT,
content_type=self.content_type,
object_id=self.paper.id,
created_by=self.user,
)
self.comment = RhCommentModel.objects.create(
comment_content_json={"ops": [{"insert": "comment1"}]},
created_by=self.user,
thread=self.thread,
)

@patch("feed.signals.comment_signals.create_feed_entry")
@patch("feed.signals.comment_signals.transaction")
def test_handle_comment_created_signal(
self, mock_transaction, mock_create_feed_entry
):
"""
Test that a feed entry is created when a comment is created.
"""
# Arrange
mock_transaction.on_commit = lambda func: func()
mock_create_feed_entry.apply_async = MagicMock()

# Act
comment = RhCommentModel.objects.create(
comment_content_json={"ops": [{"insert": "comment1"}]},
created_by=self.user,
thread=self.thread,
)

# Assert
mock_create_feed_entry.apply_async.assert_has_calls(
[
call(
args=(
comment.id,
ContentType.objects.get_for_model(RhCommentModel).id,
FeedEntry.PUBLISH,
self.hub1.id,
ContentType.objects.get_for_model(Hub).id,
self.user.id,
),
priority=1,
),
call(
args=(
comment.id,
ContentType.objects.get_for_model(RhCommentModel).id,
FeedEntry.PUBLISH,
self.hub2.id,
ContentType.objects.get_for_model(Hub).id,
self.user.id,
),
priority=1,
),
]
)

@patch("feed.signals.comment_signals.delete_feed_entry")
@patch("feed.signals.comment_signals.transaction")
def test_handle_comment_removed_signal(
self, mock_transaction, mock_delete_feed_entry
):
"""
Test that a feed entry is deleted when a comment is removed.
"""
# Arrange
mock_transaction.on_commit = lambda func: func()
mock_delete_feed_entry.apply_async = MagicMock()

# Act
self.comment.is_removed = True
self.comment.save()

# Assert
mock_delete_feed_entry.apply_async.assert_has_calls(
[
call(
args=(
self.comment.id,
ContentType.objects.get_for_model(RhCommentModel).id,
self.hub1.id,
ContentType.objects.get_for_model(Hub).id,
),
priority=1,
),
call(
args=(
self.comment.id,
ContentType.objects.get_for_model(RhCommentModel).id,
self.hub2.id,
ContentType.objects.get_for_model(Hub).id,
),
priority=1,
),
]
)

0 comments on commit b1ceb29

Please sign in to comment.