diff --git a/app/grants/admin.py b/app/grants/admin.py index 5d56ddcb39d..84632975b8b 100644 --- a/app/grants/admin.py +++ b/app/grants/admin.py @@ -95,7 +95,8 @@ class GrantAdmin(GeneralAdmin): 'subscriptions_links', 'contributions_links', 'logo', 'logo_svg', 'image_css', 'link', 'clr_prediction_curve', 'hidden', 'next_clr_calc_date', 'last_clr_calc_date', 'metadata', 'twitter_handle_1', 'twitter_handle_2', 'view_count', 'is_clr_eligible', 'in_active_clrs', - 'last_update', 'funding_info', 'twitter_verified', 'twitter_verified_by', 'twitter_verified_at', 'stats_history' + 'last_update', 'funding_info', 'twitter_verified', 'twitter_verified_by', 'twitter_verified_at', 'stats_history', + 'zcash_payout_address' ] readonly_fields = [ 'logo_svg_asset', 'logo_asset', diff --git a/app/grants/urls.py b/app/grants/urls.py index a986494d8f3..223b0ba3636 100644 --- a/app/grants/urls.py +++ b/app/grants/urls.py @@ -20,12 +20,12 @@ from django.urls import path, re_path from grants.views import ( - add_grant_from_collection, bulk_fund, bulk_grants_for_cart, clr_grants, contribute_to_grant_v1, flag, get_collection, get_collections_list, - get_grant_payload, get_grants, get_replaced_tx, grant_activity, grant_categories, grant_details, grant_fund, - grant_new, grant_new_whitelabel, grants, grants_addr_as_json, grants_bulk_add, grants_by_grant_type, - grants_cart_view, grants_clr, grants_stats_view, invoice, leaderboard, new_matching_partner, profile, quickstart, - remove_grant_from_collection, save_collection, subscription_cancel, toggle_grant_favorite, verify_grant, - zksync_get_interrupt_status, zksync_set_interrupt_status, + add_grant_from_collection, bulk_fund, bulk_grants_for_cart, clr_grants, contribute_to_grants_v1, flag, + get_collection, get_collections_list, get_grant_payload, get_grants, get_replaced_tx, grant_activity, + grant_categories, grant_details, grant_fund, grant_new, grant_new_whitelabel, grants, grants_addr_as_json, + grants_bulk_add, grants_by_grant_type, grants_cart_view, grants_clr, grants_stats_view, invoice, leaderboard, + new_matching_partner, profile, quickstart, remove_grant_from_collection, save_collection, subscription_cancel, + toggle_grant_favorite, verify_grant, zksync_get_interrupt_status, zksync_set_interrupt_status, ) app_name = 'grants' @@ -77,5 +77,5 @@ path('v1/api/collections//grants/add', add_grant_from_collection, name='add_grant'), path('v1/api/collections//grants/remove', remove_grant_from_collection, name='remove_grant'), path('v1/api/collections/', get_collections_list, name='get_collection'), - path('v1/api//contribute', contribute_to_grant_v1, name='contribute_to_grant_v1') + path('v1/api/contribute', contribute_to_grants_v1, name='contribute_to_grants_v1') ] diff --git a/app/grants/views.py b/app/grants/views.py index 8562fd26e2e..4e813864fd4 100644 --- a/app/grants/views.py +++ b/app/grants/views.py @@ -1256,7 +1256,7 @@ def grant_new(request): if request.method == 'POST': from grants.utils import add_grant_to_active_clrs - + response = { 'status': 400, 'message': 'error: Bad Request. Unable to create grant' @@ -2340,7 +2340,7 @@ def add_grant_from_collection(request, collection_id): @csrf_exempt @require_POST # @login_required -def contribute_to_grant_v1(request, grant_id): +def contribute_to_grants_v1(request): response = { 'status': 400, @@ -2366,102 +2366,118 @@ def contribute_to_grant_v1(request, grant_id): response['message'] = 'error: contribution to a grant is a POST operation' return JsonResponse(response) - if not grant_id: - response['message'] = 'error: grant_id is mandatory param' - return JsonResponse(response) + request_body = json.loads(request.body.decode("utf-8")) - try: - grant = Grant.objects.get(pk=grant_id) - except Grant.DoesNotExist: - response['message'] = 'error: invalid grant' + contributions = request_body.get('contributions', None) + if not contributions: + response['message'] = 'error: contributions in a mandatory parameter' return JsonResponse(response) - if grant.link_to_new_grant or not grant.active: - response['message'] = 'error: grant is no longer active' - return JsonResponse(response) - if is_grant_team_member(grant, profile): - response['message'] = 'error: team members cannot contribute to own grant' - return JsonResponse(response) - - contributor_address = request.POST.get('contributor_address', None) - if not contributor_address: - response['message'] = 'error: contributor_address is mandatory param' - return JsonResponse(response) + failed_contributions = [] - token_symbol = request.POST.get('token_symbol', None) - if not token_symbol: - response['message'] = 'error: token_symbol is mandatory param' - return JsonResponse(response) - - amount_per_period = request.POST.get('amount_per_period', None) - if not amount_per_period: - response['message'] = 'error: amount_per_period is mandatory param' - return JsonResponse(response) + for contribution in contributions: - tenant = request.POST.get('tenant', None) - if not tenant: - response['message'] = 'error: tenant is mandatory param' - return JsonResponse(response) + grant_id = contribution.get('grant_id', None) + if not grant_id: + response['message'] = 'error: grant_id is mandatory param' + return JsonResponse(response) - if not tenant in ['ETH', 'ZCASH']: - response['message'] = 'error: tenant chain is not supported.' - return JsonResponse(response) + try: + grant = Grant.objects.get(pk=grant_id) + except Grant.DoesNotExist: + response['message'] = f'error: invalid grant {grant_id}' + return JsonResponse(response) + if grant.link_to_new_grant or not grant.active: + response['message'] = f'error: grant {grant_id} is no longer active' + return JsonResponse(response) - tx_id = request.POST.get('tx_id', None) - comment = request.POST.get('comment', None) - network = grant.network - hide_wallet_address = request.POST.get('hide_wallet_address', None) + if is_grant_team_member(grant, profile): + response['message'] = f'error: team members cannot contribute to own grant {grant_id}' + return JsonResponse(response) - try: + contributor_address = contribution.get('contributor_address', None) + if not contributor_address: + response['message'] = f'error: contributor_address is mandatory param for grant {grant_id}' + return JsonResponse(response) - # step 2 : create 1 time subscription - subscription = Subscription() - subscription.contributor_address = contributor_address - subscription.amount_per_period = amount_per_period - subscription.token_symbol = token_symbol - subscription.contributor_profile = profile - subscription.grant = grant - subscription.comments = comment - subscription.network = network - subscription.tenant = tenant - # recurring payments set to none - subscription.active = False - subscription.real_period_seconds = 0 - subscription.frequency = 1 - subscription.frequency_unit ='days' - subscription.token_address = '' - subscription.gas_price = 0 - subscription.new_approve_tx_id = '' - subscription.split_tx_id = '' + token_symbol = contribution.get('token_symbol', None) + if not token_symbol: + response['message'] = f'error: token_symbol is mandatory param for grant {grant_id}' + return JsonResponse(response) - subscription.error = True # cancel subs so it doesnt try to bill again - subscription.subminer_comments = "skipping subminer bc this is subscriptions aren't supported for this flow" + amount_per_period = contribution.get('amount_per_period', None) + if not amount_per_period: + response['message'] = f'error: amount_per_period is mandatory param for grant {grant_id}' + return JsonResponse(response) - subscription.save() + tenant = contribution.get('tenant', None) + if not tenant: + response['message'] = f'error: tenant is mandatory param for grant {grant_id}' + return JsonResponse(response) + if not tenant in ['ETH', 'ZCASH']: + response['message'] = f'error: tenant chain is not supported for grant {grant_id}' + return JsonResponse(response) - # step 3: create contribution + fire celery - contribution = subscription.create_contribution(tx_id) - sync_payout(contribution) + tx_id = contribution.get('tx_id', None) + comment = contribution.get('comment', None) + network = grant.network + hide_wallet_address = contribution.get('hide_wallet_address', None) + try: - # step 4 : other tasks - if hide_wallet_address and not profile.hide_wallet_address: - profile.hide_wallet_address = hide_wallet_address - profile.save() + # step 2 : create 1 time subscription + subscription = Subscription() + subscription.contributor_address = contributor_address + subscription.amount_per_period = amount_per_period + subscription.token_symbol = token_symbol + subscription.contributor_profile = profile + subscription.grant = grant + subscription.comments = comment + subscription.network = network + subscription.tenant = tenant + # recurring payments set to none + subscription.active = False + subscription.real_period_seconds = 0 + subscription.frequency = 1 + subscription.frequency_unit ='days' + subscription.token_address = '' + subscription.gas_price = 0 + subscription.new_approve_tx_id = '' + subscription.split_tx_id = '' + + subscription.error = True # cancel subs so it doesnt try to bill again + subscription.subminer_comments = "skipping subminer as subscriptions aren't supported for this flow" + + subscription.save() + + # step 3: create contribution + fire celery + contribution = subscription.create_contribution(tx_id) + sync_payout(contribution) + + + # step 4 : other tasks + if hide_wallet_address and not profile.hide_wallet_address: + profile.hide_wallet_address = hide_wallet_address + profile.save() + + except Exception as error: + failed_contributions.append({ + 'grant_id': grant_id, + 'message': f'grant contribution not recorded', + 'error': error + }) - except Exception as error: + if len(failed_contributions): response = { 'status': 500, - 'message': 'grant contribution not recorded', - 'error': error + 'failed_contributions': failed_contributions + } + else: + response = { + 'status': 204, + 'message': 'grant contributions recorded' } - - response = { - 'status': 204, - 'message': 'grant contribution recorded' - } - return JsonResponse(response)