Skip to content

Commit 63cbf23

Browse files
committed
Merge branch 'stable'
2 parents 70f4271 + 8210cb4 commit 63cbf23

File tree

10 files changed

+271
-12
lines changed

10 files changed

+271
-12
lines changed

app/assets/v2/js/grants/_new.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ if (document.getElementById('gc-new-grant')) {
482482
return this.submitted && this.step === this.currentSteps.length && Object.keys(this.errors).length === 0;
483483
},
484484
grantTagOptions() {
485+
const sorted_tags = this.grant_tags.sort((a, b) => a.id - b.id);
486+
const next_id = sorted_tags[sorted_tags.length-1].id + 1;
485487
const all_tags = this.grant_tags.sort((a, b) => b.is_eligibility_tag - a.is_eligibility_tag);
486488
const first_discovery = (tag) => tag.is_eligibility_tag === 0;
487489

@@ -490,12 +492,13 @@ if (document.getElementById('gc-new-grant')) {
490492
name: 'eligibility tags'.toUpperCase(),
491493
is_eligibility_tag: 'label'
492494
});
493-
495+
494496
all_tags.splice(all_tags.findIndex(first_discovery), 0, {
495-
id: all_tags.length + 1,
497+
id: next_id,
496498
name: 'discovery tags'.toUpperCase(),
497499
is_eligibility_tag: 'label'
498500
});
501+
499502
return all_tags;
500503
},
501504
queryParams() {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from django.core.management.base import BaseCommand
2+
3+
from grants.models import Grant, GrantTag
4+
5+
class Command(BaseCommand):
6+
7+
help = 'updates all approved grants to include the main-round tag'
8+
9+
def handle(self, *args, **options):
10+
# main-round tag
11+
tag = GrantTag.objects.get(pk=62)
12+
grants = Grant.objects.filter(active=True, hidden=False, is_clr_eligible=True)
13+
14+
print(f"adding main-round tag to {grants.count()} Grants:")
15+
16+
# for every eligible grant
17+
for grant in grants:
18+
try:
19+
# update the tag record to include the main round tag
20+
grant.tags.add(tag)
21+
grant.save()
22+
except Exception as e:
23+
pass
24+
25+
print("\n - done\n")

app/grants/admin.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class FlagAdmin(admin.ModelAdmin):
5151
ordering = ['-id']
5252
raw_id_fields = ['profile', 'grant']
5353
readonly_fields = ['grant_link']
54-
54+
5555
def grant_link(self, obj):
5656
return format_html("<a href='/grants/{id}/{slug}' target=\"_blank\">Grant Details</a>", id=obj.grant.id, slug=obj.grant.slug)
5757

@@ -105,9 +105,10 @@ class GrantAdmin(GeneralAdmin):
105105
'title', 'is_grant_idle',
106106
'active', 'visible', 'is_clr_eligible',
107107
'migrated_to', 'region',
108-
'grant_type', 'tags', 'tag_eligibility_reason','description', 'description_rich', 'github_project_url', 'reference_url', 'admin_address',
109-
'amount_received', 'amount_received_in_round', 'monthly_amount_subscribed', 'defer_clr_to',
110-
'deploy_tx_id', 'cancel_tx_id', 'admin_profile', 'token_symbol',
108+
'grant_type', 'tags_requested', 'tags', 'tag_eligibility_reason',
109+
'description', 'description_rich', 'github_project_url', 'reference_url',
110+
'admin_address', 'amount_received', 'amount_received_in_round', 'monthly_amount_subscribed',
111+
'defer_clr_to', 'deploy_tx_id', 'cancel_tx_id', 'admin_profile', 'token_symbol',
111112
'token_address', 'contract_address', 'contract_version', 'network', 'required_gas_price', 'logo_svg_asset',
112113
'logo_asset', 'created_on', 'modified_on', 'team_member_list',
113114
'subscriptions_links', 'contributions_links', 'logo', 'logo_svg', 'image_css',
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 2.2.24 on 2022-08-09 11:34
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('grants', '0145_grant_tag_eligibility_reason'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='grant',
15+
name='tags_requested',
16+
field=models.ManyToManyField(blank=True, related_name='tags_requested', to='grants.GrantTag'),
17+
),
18+
migrations.AlterField(
19+
model_name='grant',
20+
name='tags',
21+
field=models.ManyToManyField(blank=True, related_name='tags', to='grants.GrantTag'),
22+
),
23+
]

app/grants/models/grant.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ class Meta:
584584
db_index=True,
585585
)
586586
categories = models.ManyToManyField('GrantCategory', blank=True) # TODO: REMOVE
587-
tags = models.ManyToManyField('GrantTag', blank=True)
587+
tags = models.ManyToManyField('GrantTag', blank=True, related_name='tags')
588+
tags_requested = models.ManyToManyField('GrantTag', blank=True, related_name='tags_requested')
588589
tag_eligibility_reason = models.TextField(default='', blank=True, help_text=_('Eligibility Tag Reasoning'))
589590
twitter_handle_1 = models.CharField(default='', max_length=255, help_text=_('Grants twitter handle'), blank=True)
590591
twitter_handle_2 = models.CharField(default='', max_length=255, help_text=_('Grants twitter handle'), blank=True)

app/grants/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
leaderboard, manage_ethereum_cart_data, matching_funds, profile, remove_grant_from_collection, save_collection,
3434
toggle_grant_favorite, upload_sybil_csv, verify_grant,
3535
)
36+
from grants.views_api_vc import contributor_statistics, grantee_statistics
3637

3738
app_name = 'grants/'
3839
urlpatterns = [
@@ -126,4 +127,7 @@
126127
path('v1/api/upload_sybil_csv', upload_sybil_csv, name='upload_sybil_csv'),
127128
path('v1/api/ingest_merkle_claim_to_clr_match', ingest_merkle_claim_to_clr_match, name='ingest_merkle_claim_to_clr_match'),
128129

130+
# VC verification API (when issuing a VC)
131+
path('v1/api/vc/contributor_statistics', contributor_statistics, name='contributor_statistics'),
132+
path('v1/api/vc/grantee_statistics', grantee_statistics, name='grantee_statistics'),
129133
]

app/grants/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ def save_grant_to_notion(grant):
321321
# fully qualified url
322322
fullUrl = BASE_URL.rstrip('/') + grant.url
323323
grant_tags = []
324-
for tag in grant.tags.all():
324+
for tag in grant.tags_requested.all():
325325
grant_tags.append(str(tag))
326326

327327
# write to NOTION_SYBIL_DB following the defined schema (returns dict of new object)
@@ -494,10 +494,10 @@ def bsci_script(csv: str) -> tuple:
494494
# Assign final `is_sybil` markings according to a priorization criteria
495495
df.loc[labels_by_evaluation, 'is_sybil'] = df[labels_by_evaluation].evaluation_score > EVAL_THRESHOLD
496496
df.loc[labels_by_evaluation, 'label'] = "Human Evaluation"
497-
497+
498498
df.loc[labels_by_heuristic, 'is_sybil'] = df[labels_by_heuristic].heuristic_score > HEURISTIC_THRESHOLD
499499
df.loc[labels_by_heuristic, 'label'] = "Heuristics"
500-
500+
501501
df.loc[labels_by_prediction, 'is_sybil'] = df[labels_by_prediction].prediction_score > ML_THRESHOLD
502502
df.loc[labels_by_prediction, 'label'] = "ML Prediction"
503503

app/grants/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3347,7 +3347,7 @@ def post(self, request):
33473347
for tag_id in tag_ids:
33483348
try:
33493349
tag = GrantTag.objects.get(pk=tag_id)
3350-
grant.tags.add(tag)
3350+
grant.tags_requested.add(tag)
33513351
except Exception as e:
33523352
pass
33533353

app/grants/views_api_vc.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# -*- coding: utf-8 -*-
2+
"""Define the Grant views for API used by the passport IAM
3+
4+
Copyright (C) 2021 Gitcoin Core
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License as published
8+
by the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU Affero General Public License for more details.
15+
16+
You should have received a copy of the GNU Affero General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
19+
"""
20+
import logging
21+
22+
from django.conf import settings
23+
from django.db.models import Sum
24+
from django.http import JsonResponse
25+
from django.utils.translation import gettext_lazy as _
26+
27+
from grants.models import Contribution, Grant
28+
from perftools.models import StaticJsonEnv
29+
from townsquare.models import SquelchProfile
30+
31+
logger = logging.getLogger(__name__)
32+
33+
34+
def ami_api_token_required(func):
35+
def decorator(request, *args, **kwargs):
36+
try:
37+
apiToken = StaticJsonEnv.objects.get(key="AMI_API_TOKEN")
38+
expectedToken = apiToken.data["token"]
39+
receivedToken = request.headers.get("Authorization")
40+
41+
if receivedToken:
42+
# Token shall look like "token <bearer token>", and we need only the <bearer token> part
43+
receivedToken = receivedToken.split(" ")[1]
44+
45+
if expectedToken == receivedToken:
46+
return func(request, *args, **kwargs)
47+
else:
48+
return JsonResponse(
49+
{
50+
"error": "Access denied",
51+
},
52+
status=403,
53+
)
54+
except Exception as e:
55+
logger.error("Error in ami_api_token_required %s", e)
56+
return JsonResponse(
57+
{
58+
"error": "An unexpected error occured",
59+
},
60+
status=500,
61+
)
62+
63+
return decorator
64+
65+
66+
@ami_api_token_required
67+
def contributor_statistics(request):
68+
handle = request.GET.get("handle")
69+
70+
if not handle:
71+
return JsonResponse(
72+
{"error": "Bad request, 'handle' parameter is missing or invalid"},
73+
status=400,
74+
)
75+
76+
# Get number of grants the user contributed to
77+
num_grants_contribute_to = (
78+
Contribution.objects.filter(profile_for_clr__handle=handle, success=True)
79+
.order_by("grant_id")
80+
.distinct("grant_id")
81+
.count()
82+
)
83+
84+
# Get the number of grants the user contributed to
85+
num_rounds_contribute_to = (
86+
Contribution.objects.filter(
87+
success=True,
88+
subscription__contributor_profile__handle=handle,
89+
subscription__network="mainnet",
90+
subscription__grant__clr_calculations__latest=True,
91+
)
92+
.order_by("subscription__grant__clr_calculations__grantclr__round_num")
93+
.distinct("subscription__grant__clr_calculations__grantclr__round_num")
94+
.count()
95+
)
96+
97+
total_contribution_amount = Contribution.objects.filter(
98+
profile_for_clr__handle=handle, success=True
99+
).aggregate(Sum("amount_per_period_usdt"))["amount_per_period_usdt__sum"]
100+
total_contribution_amount = (
101+
total_contribution_amount if total_contribution_amount is not None else 0
102+
)
103+
104+
# GR14 contributor (and not squelched by FDD)
105+
profile_squelch = SquelchProfile.objects.filter(
106+
profile__handle=handle, active=True
107+
).values_list("profile_id", flat=True)
108+
109+
num_gr14_contributions = (
110+
Contribution.objects.filter(
111+
success=True,
112+
subscription__contributor_profile__handle=handle,
113+
subscription__network="mainnet",
114+
subscription__grant__clr_calculations__latest=True,
115+
subscription__grant__clr_calculations__grantclr__round_num=14,
116+
)
117+
.exclude(subscription__contributor_profile_id__in=profile_squelch)
118+
.count()
119+
)
120+
121+
return JsonResponse(
122+
{
123+
"num_grants_contribute_to": num_grants_contribute_to,
124+
"num_rounds_contribute_to": num_rounds_contribute_to,
125+
"total_contribution_amount": total_contribution_amount,
126+
"is_gr14_contributor": num_gr14_contributions > 0,
127+
}
128+
)
129+
130+
131+
@ami_api_token_required
132+
def grantee_statistics(request):
133+
handle = request.GET.get("handle")
134+
135+
if not handle:
136+
return JsonResponse(
137+
{"error": "Bad request, 'handle' parameter is missing or invalid"},
138+
status=400,
139+
)
140+
141+
# Get number of owned grants
142+
num_owned_grants = Grant.objects.filter(
143+
admin_profile__handle=handle,
144+
hidden=False,
145+
active=True,
146+
is_clr_eligible=True,
147+
).count()
148+
149+
# Get the total amount of contrinutors for ane users grants that where not squelched and are not the owner himself
150+
all_squelched = SquelchProfile.objects.filter(active=True).values_list(
151+
"profile_id", flat=True
152+
)
153+
num_grant_contributors = (
154+
Contribution.objects.filter(
155+
success=True,
156+
subscription__network="mainnet",
157+
subscription__grant__hidden=False,
158+
subscription__grant__active=True,
159+
subscription__grant__is_clr_eligible=True,
160+
subscription__grant__admin_profile__handle=handle,
161+
)
162+
.exclude(subscription__contributor_profile_id__in=all_squelched)
163+
.exclude(subscription__contributor_profile__handle=handle)
164+
.order_by("subscription__contributor_profile_id")
165+
.distinct("subscription__contributor_profile_id")
166+
.count()
167+
)
168+
169+
# Get the total amount of contributions received by the owned grants (excluding the contributions made by the owner)
170+
total_contribution_amount = (
171+
Contribution.objects.filter(
172+
success=True,
173+
subscription__network="mainnet",
174+
subscription__grant__hidden=False,
175+
subscription__grant__active=True,
176+
subscription__grant__is_clr_eligible=True,
177+
subscription__grant__admin_profile__handle=handle,
178+
)
179+
.exclude(subscription__contributor_profile__handle=handle)
180+
.aggregate(Sum("amount_per_period_usdt"))["amount_per_period_usdt__sum"]
181+
)
182+
total_contribution_amount = (
183+
total_contribution_amount if total_contribution_amount is not None else 0
184+
)
185+
186+
# [IAM] As an IAM server, I want to issue stamps for grant owners whose project have tagged matching-eligibel in an eco-system and/or cause round
187+
num_grants_in_eco_and_cause_rounds = Grant.objects.filter(
188+
admin_profile__handle=handle,
189+
hidden=False,
190+
active=True,
191+
is_clr_eligible=True,
192+
clr_calculations__grantclr__type__in=["ecosystem", "cause"],
193+
).count()
194+
195+
return JsonResponse(
196+
{
197+
"num_owned_grants": num_owned_grants,
198+
"num_grant_contributors": num_grant_contributors,
199+
"num_grants_in_eco_and_cause_rounds": num_grants_in_eco_and_cause_rounds,
200+
"total_contribution_amount": total_contribution_amount,
201+
}
202+
)

docker-compose-celery.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ services:
99
max-file: '10'
1010
driver: json-file
1111

12-
image: gitcoin/web:a68541e6fe
12+
image: gitcoin/web:0b8eae8cd2
1313

1414
volumes:
1515
# - .:/code

0 commit comments

Comments
 (0)