From 08f2c3ab64b84098a7739cc9086980990f9af9da Mon Sep 17 00:00:00 2001 From: nicktytarenko Date: Tue, 28 Jan 2025 20:49:56 +0000 Subject: [PATCH] WIP - create util function and call it within the transaction + response --- src/purchase/utils.py | 67 +++++++++++++++++++ src/purchase/views/fundraise_view.py | 58 ++++++---------- .../views/researchhub_post_views.py | 18 ++++- 3 files changed, 105 insertions(+), 38 deletions(-) diff --git a/src/purchase/utils.py b/src/purchase/utils.py index fb2e8ed15d..6841093c4c 100644 --- a/src/purchase/utils.py +++ b/src/purchase/utils.py @@ -1,8 +1,14 @@ +import decimal import time +from rest_framework.response import Response + +from purchase.related_models.constants.currency import USD from reputation.distributions import create_purchase_distribution from reputation.distributor import Distributor from reputation.models import Escrow +from researchhub_document.related_models.constants.document_type import PREREGISTRATION +from utils.sentry import log_error def distribute_support_to_authors(paper, purchase, amount): @@ -31,3 +37,64 @@ def store_leftover_paper_support(paper, purchase, leftover_amount): item=paper, hold_type=Escrow.AUTHOR_RSC, ) + + +def create_fundraise_with_escrow( + user, unified_document, goal_amount, goal_currency=USD +): + """ + Helper function to create a fundraise with its associated escrow. + Returns (fundraise, error_response) tuple where error_response is None if successful + + Note: This function should be called within a transaction.atomic() block + """ + from django.contrib.contenttypes.models import ContentType + + from purchase.models import Fundraise + from reputation.models import Escrow + + # Validate inputs + if not unified_document.document_type == PREREGISTRATION: + return None, Response( + {"message": "Fundraise must be for a preregistration"}, status=400 + ) + + try: + goal_amount = decimal.Decimal(goal_amount) + if goal_amount <= 0: + return None, Response( + {"message": "goal_amount must be greater than 0"}, status=400 + ) + except Exception as e: + log_error(e) + return None, Response({"detail": "Invalid goal_amount"}, status=400) + + if goal_currency != USD: + return None, Response({"message": "goal_currency must be USD"}, status=400) + + # Check if fundraise already exists + existing_fundraise = Fundraise.objects.filter( + unified_document=unified_document + ).first() + if existing_fundraise: + return None, Response({"message": "Fundraise already exists"}, status=400) + + # Create fundraise object + fundraise = Fundraise.objects.create( + created_by=user, + unified_document=unified_document, + goal_amount=goal_amount, + goal_currency=goal_currency, + status=Fundraise.OPEN, + ) + # Create escrow object + escrow = Escrow.objects.create( + created_by=user, + hold_type=Escrow.FUNDRAISE, + content_type=ContentType.objects.get_for_model(Fundraise), + object_id=fundraise.id, + ) + fundraise.escrow = escrow + fundraise.save() + + return fundraise, None diff --git a/src/purchase/views/fundraise_view.py b/src/purchase/views/fundraise_view.py index 2151537c2c..fa22fc7b82 100644 --- a/src/purchase/views/fundraise_view.py +++ b/src/purchase/views/fundraise_view.py @@ -16,6 +16,7 @@ from purchase.related_models.constants.currency import RSC, USD from purchase.serializers.fundraise_serializer import DynamicFundraiseSerializer from purchase.serializers.purchase_serializer import DynamicPurchaseSerializer +from purchase.utils import create_fundraise_with_escrow from reputation.models import BountyFee, Escrow from reputation.utils import calculate_bounty_fees, deduct_bounty_fees from researchhub_document.models import ResearchhubPost, ResearchhubUnifiedDocument @@ -93,18 +94,6 @@ def create(self, request, *args, **kwargs): # Validate body if goal_amount is None: return Response({"message": "goal_amount is required"}, status=400) - try: - goal_amount = decimal.Decimal(goal_amount) - if goal_amount <= 0: - return Response( - {"message": "goal_amount must be greater than 0"}, status=400 - ) - except Exception as e: - log_error(e) - return Response({"detail": "Invalid goal_amount"}, status=400) - # only allow USD for now - if goal_currency != USD: - return Response({"message": "goal_currency must be USD"}, status=400) if unified_document_id is None and post_id is None: return Response( {"message": "unified_document_id or post_id is required"}, status=400 @@ -135,19 +124,6 @@ def create(self, request, *args, **kwargs): unified_document = post.unified_document except ResearchhubPost.DoesNotExist: return Response({"message": "Post does not exist"}, status=400) - - # Check if there is an existing fundraise for this unified_document - existing_fundraise = Fundraise.objects.filter( - unified_document=unified_document - ).first() - if existing_fundraise: - return Response({"message": "Fundraise already exists"}, status=400) - # Must be a preregistration - if unified_document.document_type != PREREGISTRATION: - return Response( - {"message": "Fundraise must be for a preregistration"}, status=400 - ) - # Get recipient user object recipient_user = None if recipient_user_id: @@ -160,22 +136,30 @@ def create(self, request, *args, **kwargs): with transaction.atomic(): # Create fundraise object - fundraise = Fundraise.objects.create( - created_by=recipient_user, + # fundraise = Fundraise.objects.create( + # created_by=recipient_user, + # unified_document=unified_document, + # goal_amount=goal_amount, + # goal_currency=goal_currency, + # status=Fundraise.OPEN, + # ) + # # Create escrow object + # escrow = Escrow.objects.create( + # created_by=recipient_user, + # hold_type=Escrow.FUNDRAISE, + # content_type=ContentType.objects.get_for_model(Fundraise), + # object_id=fundraise.id, + # ) + # fundraise.escrow = escrow + # fundraise.save() + fundraise, error_response = create_fundraise_with_escrow( + user=recipient_user, unified_document=unified_document, goal_amount=goal_amount, goal_currency=goal_currency, - status=Fundraise.OPEN, - ) - # Create escrow object - escrow = Escrow.objects.create( - created_by=recipient_user, - hold_type=Escrow.FUNDRAISE, - content_type=ContentType.objects.get_for_model(Fundraise), - object_id=fundraise.id, ) - fundraise.escrow = escrow - fundraise.save() + if error_response: + return error_response context = self.get_serializer_context() serializer = self.get_serializer(fundraise, context=context) diff --git a/src/researchhub_document/views/researchhub_post_views.py b/src/researchhub_document/views/researchhub_post_views.py index 59921227c0..fad37d9ea0 100644 --- a/src/researchhub_document/views/researchhub_post_views.py +++ b/src/researchhub_document/views/researchhub_post_views.py @@ -12,6 +12,9 @@ from hub.models import Hub from note.related_models.note_model import Note from purchase.models import Balance, Purchase +from purchase.related_models.constants.currency import USD +from purchase.serializers.fundraise_serializer import DynamicFundraiseSerializer +from purchase.utils import create_fundraise_with_escrow from researchhub.settings import CROSSREF_DOI_RSC_FEE, TESTING from researchhub_document.models import ResearchhubPost, ResearchhubUnifiedDocument from researchhub_document.permissions import HasDocumentEditingPermission @@ -168,6 +171,17 @@ def create_researchhub_post(self, request): rh_post.authors.set(authors) self.add_upvote(created_by, rh_post) + fundraise = None + if fundraise_data := data.get("fundraise"): + fundraise, error_response = create_fundraise_with_escrow( + user=created_by, + unified_document=unified_document, + goal_amount=fundraise_data.get("goal_amount"), + goal_currency=fundraise_data.get("goal_currency", USD), + ) + if error_response: + return error_response + if not TESTING: if document_type in RESEARCHHUB_POST_DOCUMENT_TYPES: rh_post.discussion_src.save(file_name, full_src_file) @@ -202,7 +216,9 @@ def create_researchhub_post(self, request): ) ) - return Response(ResearchhubPostSerializer(rh_post).data, status=200) + response_data = ResearchhubPostSerializer(rh_post).data + response_data["fundraise"] = DynamicFundraiseSerializer(fundraise).data + return Response(response_data, status=200) except (KeyError, TypeError) as exception: log_error(exception)