diff --git a/src/feed/serializers.py b/src/feed/serializers.py index 01489e024c..7bd3514ac6 100644 --- a/src/feed/serializers.py +++ b/src/feed/serializers.py @@ -966,61 +966,6 @@ def _get_grant_image(grant): return None -class ActivityFeedEntrySerializer(FeedEntrySerializer): - """ - Serializer for activity feed entries that includes fundraise contributions. - """ - - contributions = serializers.SerializerMethodField() - - class Meta: - model = FeedEntry - fields = FeedEntrySerializer.Meta.fields + [ - "contributions", - ] - - def get_contributions(self, obj): - """ - Return fundraise contributors for entries whose unified document has a - fundraise. - """ - if not obj.unified_document: - return None - - fundraises = getattr(obj.unified_document, "prefetched_fundraises", None) - if fundraises is None: - fundraises = list(obj.unified_document.fundraises.all()) - - if not fundraises: - return None - - fundraise = fundraises[0] - aggregated = fundraise.get_contributors_summary() - - result = [] - for entry in aggregated.top: - serializer = SimpleUserSerializer(entry.user) - user_result = serializer.data - user_result["total_contribution"] = { - "rsc": entry.total_rsc, - "usd": entry.total_usd, - } - user_result["contributions"] = [ - { - "amount": contribution.amount, - "currency": contribution.currency, - "date": contribution.date, - } - for contribution in entry.contributions - ] - result.append(user_result) - - return { - "total": aggregated.total, - "top": result, - } - - class GrantFeedEntrySerializer(FeedEntrySerializer): """Serializer for grant feed entries""" diff --git a/src/feed/tests/test_serializers.py b/src/feed/tests/test_serializers.py index cb4a779bf4..db06cf7f6a 100644 --- a/src/feed/tests/test_serializers.py +++ b/src/feed/tests/test_serializers.py @@ -8,7 +8,7 @@ from feed.models import FeedEntry from feed.serializers import ( - ActivityFeedEntrySerializer, + CommentSerializer, ContentObjectSerializer, FeedEntrySerializer, @@ -2447,204 +2447,6 @@ def test_fundraise_without_application_has_no_associated_grants( self.assertEqual(serializer.data["associated_grants"], []) -class ActivityFeedEntrySerializerTests(AWSMockTestCase): - """ - Test cases for the ActivityFeedEntrySerializer. - """ - - def setUp(self): - super().setUp() - self.user = create_random_default_user("activity_feed_user") - - def _create_feed_entry(self, unified_doc, post): - return FeedEntry.objects.create( - content_type=ContentType.objects.get_for_model(ResearchhubPost), - object_id=post.id, - user=self.user, - action="PUBLISH", - action_date=post.created_date, - unified_document=unified_doc, - ) - - def _create_fundraise_post(self): - unified_doc = ResearchhubUnifiedDocument.objects.create( - document_type=document_type.PREREGISTRATION, - ) - post = ResearchhubPost.objects.create( - title="Prereg Post", - created_by=self.user, - document_type=document_type.PREREGISTRATION, - renderable_text="A preregistration post", - unified_document=unified_doc, - ) - return unified_doc, post - - def _create_purchase(self, fundraise, user, amount): - ct = ContentType.objects.get_for_model(Fundraise) - return Purchase.objects.create( - user=user, - content_type=ct, - object_id=fundraise.id, - purchase_type=Purchase.FUNDRAISE_CONTRIBUTION, - purchase_method=Purchase.OFF_CHAIN, - amount=str(amount), - ) - - def _create_usd_contribution(self, fundraise, user, amount_cents): - return UsdFundraiseContribution.objects.create( - user=user, - fundraise=fundraise, - amount_cents=amount_cents, - fee_cents=int(amount_cents * 0.09), - origin_fund_id="test-origin", - destination_org_id="test-destination", - ) - - def test_contributions_none_without_fundraise(self): - """Feed entry on a regular post should have contributions=None.""" - # Arrange - unified_doc = ResearchhubUnifiedDocument.objects.create( - document_type=document_type.DISCUSSION, - ) - post = ResearchhubPost.objects.create( - title="Discussion Post", - created_by=self.user, - document_type=document_type.DISCUSSION, - renderable_text="Just a discussion", - unified_document=unified_doc, - ) - feed_entry = self._create_feed_entry(unified_doc, post) - - # Act - serializer = ActivityFeedEntrySerializer(feed_entry) - - # Assert - self.assertIsNone(serializer.data["contributions"]) - - @patch( - "purchase.related_models.rsc_exchange_rate_model.RscExchangeRate.get_latest_exchange_rate" - ) - def test_contributions_empty_with_fundraise_no_purchases(self, mock_usd_to_rsc): - """Fundraise with no purchases should return total=0, top=[].""" - # Arrange - mock_usd_to_rsc.return_value = 1.0 - - unified_doc, post = self._create_fundraise_post() - Fundraise.objects.create( - unified_document=unified_doc, - created_by=self.user, - goal_amount=Decimal("100.00"), - goal_currency=USD, - status=Fundraise.OPEN, - ) - feed_entry = self._create_feed_entry(unified_doc, post) - - # Act - serializer = ActivityFeedEntrySerializer(feed_entry) - contributions = serializer.data["contributions"] - - # Assert - self.assertEqual(contributions["total"], 0) - self.assertEqual(contributions["top"], []) - - @patch( - "purchase.related_models.rsc_exchange_rate_model.RscExchangeRate.get_latest_exchange_rate" - ) - def test_contributions_single_contributor(self, mock_usd_to_rsc): - """Single purchase should produce one contributor entry.""" - # Arrange - mock_usd_to_rsc.return_value = 1.0 - - unified_doc, post = self._create_fundraise_post() - fundraise = Fundraise.objects.create( - unified_document=unified_doc, - created_by=self.user, - goal_amount=Decimal("500.00"), - goal_currency=USD, - status=Fundraise.OPEN, - ) - self._create_purchase(fundraise, self.user, 50.0) - feed_entry = self._create_feed_entry(unified_doc, post) - - # Act - serializer = ActivityFeedEntrySerializer(feed_entry) - contributions = serializer.data["contributions"] - - # Assert - self.assertEqual(contributions["total"], 1) - self.assertEqual(len(contributions["top"]), 1) - top_entry = contributions["top"][0] - self.assertEqual(top_entry["total_contribution"]["rsc"], 50.0) - self.assertEqual(top_entry["total_contribution"]["usd"], 0) - self.assertEqual(len(top_entry["contributions"]), 1) - self.assertEqual(top_entry["contributions"][0]["amount"], 50.0) - self.assertEqual(top_entry["contributions"][0]["currency"], "RSC") - - @patch( - "purchase.related_models.rsc_exchange_rate_model.RscExchangeRate.get_latest_exchange_rate" - ) - def test_contributions_multiple_purchases_aggregated(self, mock_usd_to_rsc): - """Multiple purchases by the same user should be aggregated.""" - # Arrange - mock_usd_to_rsc.return_value = 1.0 - - unified_doc, post = self._create_fundraise_post() - fundraise = Fundraise.objects.create( - unified_document=unified_doc, - created_by=self.user, - goal_amount=Decimal("1000.00"), - goal_currency=USD, - status=Fundraise.OPEN, - ) - self._create_purchase(fundraise, self.user, 30.0) - self._create_purchase(fundraise, self.user, 70.0) - feed_entry = self._create_feed_entry(unified_doc, post) - - # Act - serializer = ActivityFeedEntrySerializer(feed_entry) - contributions = serializer.data["contributions"] - - # Assert - self.assertEqual(contributions["total"], 1) - top_entry = contributions["top"][0] - self.assertEqual(top_entry["total_contribution"]["rsc"], 100.0) - self.assertEqual(top_entry["total_contribution"]["usd"], 0) - self.assertEqual(len(top_entry["contributions"]), 2) - - @patch( - "purchase.related_models.rsc_exchange_rate_model.RscExchangeRate.get_latest_exchange_rate" - ) - def test_contributions_include_usd_contributions(self, mock_usd_to_rsc): - """RSC and USD contributions from the same user should both appear.""" - # Arrange - mock_usd_to_rsc.return_value = 1.0 - - unified_doc, post = self._create_fundraise_post() - fundraise = Fundraise.objects.create( - unified_document=unified_doc, - created_by=self.user, - goal_amount=Decimal("500.00"), - goal_currency=USD, - status=Fundraise.OPEN, - ) - contributor = create_random_default_user("mixed_contributor") - self._create_purchase(fundraise, contributor, 10.0) - self._create_usd_contribution(fundraise, contributor, 2500) - feed_entry = self._create_feed_entry(unified_doc, post) - - # Act - serializer = ActivityFeedEntrySerializer(feed_entry) - contributions = serializer.data["contributions"] - top_entry = contributions["top"][0] - - # Assert - self.assertEqual(top_entry["total_contribution"]["rsc"], 10.0) - self.assertEqual(top_entry["total_contribution"]["usd"], 25.0) - self.assertEqual(len(top_entry["contributions"]), 2) - currencies = {c["currency"] for c in top_entry["contributions"]} - self.assertEqual(currencies, {"RSC", "USD"}) - - class FundraiseContributionContentSerializerTests(AWSMockTestCase): """ Test cases for the FundraiseContributionContentSerializer. diff --git a/src/feed/views/activity_feed_view.py b/src/feed/views/activity_feed_view.py index 46ecc33ad8..2d2fb2e8ed 100644 --- a/src/feed/views/activity_feed_view.py +++ b/src/feed/views/activity_feed_view.py @@ -1,15 +1,12 @@ from django.contrib.contenttypes.models import ContentType -from django.db.models import Prefetch from rest_framework.viewsets import ModelViewSet from feed.models import FeedEntry -from feed.serializers import ActivityFeedEntrySerializer +from feed.serializers import FeedEntrySerializer from feed.views.common import FeedPagination from feed.views.feed_view_mixin import FeedViewMixin -from purchase.models import Fundraise, UsdFundraiseContribution from purchase.related_models.grant_application_model import GrantApplication from purchase.related_models.grant_model import Grant -from purchase.related_models.purchase_model import Purchase from researchhub_comment.constants.rh_comment_thread_types import ( COMMUNITY_REVIEW, PEER_REVIEW, @@ -34,7 +31,7 @@ class ActivityFeedViewSet(FeedViewMixin, ModelViewSet): returns only comments across all grant-related documents. """ - serializer_class = ActivityFeedEntrySerializer + serializer_class = FeedEntrySerializer permission_classes = [] pagination_class = FeedPagination http_method_names = ["get", "head", "options"] @@ -53,44 +50,13 @@ def list(self, request, *args, **kwargs): return response def get_queryset(self): - queryset = ( - FeedEntry.objects.select_related( - "content_type", - "unified_document", - "user", - "user__author_profile", - "user__userverification", - ) - .prefetch_related( - Prefetch( - "unified_document__fundraises", - queryset=Fundraise.objects.prefetch_related( - Prefetch( - "purchases", - queryset=Purchase.objects.select_related( - "user", - "user__author_profile", - "user__userverification", - ).order_by("-created_date"), - to_attr="prefetched_purchases", - ), - Prefetch( - "usd_contributions", - queryset=UsdFundraiseContribution.objects.select_related( - "user", - "user__author_profile", - "user__userverification", - ) - .filter(is_refunded=False) - .order_by("-created_date"), - to_attr="prefetched_usd_contributions", - ), - ), - to_attr="prefetched_fundraises", - ), - ) - .order_by("-action_date") - ) + queryset = FeedEntry.objects.select_related( + "content_type", + "unified_document", + "user", + "user__author_profile", + "user__userverification", + ).order_by("-action_date") scope = self.request.query_params.get("scope", "").lower() grant_id = self.request.query_params.get("grant_id")