Skip to content

Commit

Permalink
chore: rewrite report endpoint (#8883)
Browse files Browse the repository at this point in the history
* re-write grant report

* rate limit API endpoints

* address feeddback
  • Loading branch information
thelostone-mc authored May 27, 2021
1 parent 517e048 commit 40ca895
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 62 deletions.
193 changes: 153 additions & 40 deletions app/grants/router.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from datetime import datetime
from datetime import datetime, timedelta

from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator

import django_filters.rest_framework
from ratelimit.decorators import ratelimit
from rest_framework import routers, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
from rest_framework.response import Response

from .models import Contribution, Grant, Subscription
from .serializers import DonorSerializer, GranteeSerializer, GrantSerializer, SubscriptionSerializer
from .models import CLRMatch, Contribution, Grant, Subscription
from .serializers import (
CLRPayoutsSerializer, DonorSerializer, GrantSerializer, SubscriptionSerializer, TransactionsSerializer,
)


class SubscriptionViewSet(viewsets.ModelViewSet):
Expand Down Expand Up @@ -88,59 +93,167 @@ def get_queryset(self):

return queryset

@action(detail=False)
def report(self, request):
return Response({'error': 'reports temporarily offline'})

@action(detail=False)
def report_real(self, request):
"""Generate Grants report for an ethereum address"""
@ratelimit(key='ip', rate='5/s')
def contributions_rec_report(self, request):
"""
Genrate Grantee Report for an Grant
URL: api/v0.1/grants/contributions_rec_report/?id=<grant-id>&format=json
"""

grants_queryset = Grant.objects.all()
contributions_queryset = Contribution.objects.all()
results_limit = 30

grantee_serializer = GranteeSerializer
donor_serializer = DonorSerializer
page = self.request.query_params.get('page', '1')
if not page or not page.isdigit():
page = 1

param_keys = self.request.query_params.keys()
if 'eth_address' not in param_keys:
raise NotFound(detail='Missing required parameter: eth_address')
eth_address = self.request.query_params.get('eth_address')

# Filter Grants and Contributions by the given eth_address
grants_queryset = grants_queryset.filter(admin_address=eth_address)
contributions_queryset = contributions_queryset.filter(subscription__contributor_address=eth_address)

# Filter Grantee info by from_timestamp
format = '%Y-%m-%dT%H:%M:%SZ'
if 'from_timestamp' in param_keys:
from_timestamp = self.request.query_params.get('from_timestamp')
grant_pk = self.request.query_params.get('id')
if not grant_pk or not grant_pk.isdigit():
return Response({
'error': 'missing manadatory parameter id'
})

now = datetime.now()
to_timestamp = self.request.query_params.get('to_timestamp')
from_timestamp = self.request.query_params.get('from_timestamp')

# Check timestamp
if not from_timestamp:
from_timestamp = now - timedelta(days=30)
else:
try:
from_timestamp = datetime.strptime(from_timestamp, format)
except ValueError:
raise NotFound(detail="Please provide from_timestamp in the format: "+format)
else:
from_timestamp = datetime(1, 1, 1, 0, 0)
grants_queryset = grants_queryset.filter(subscriptions__subscription_contribution__created_on__gte=from_timestamp)
return Response({
'error': 'from_timestamp is not in format YYYY-MM-DD'
})

# Filter Grantee info by to_timestamp
if 'to_timestamp' in param_keys:
to_timestamp = self.request.query_params.get('to_timestamp')
if not to_timestamp:
to_timestamp = now
else:
try:
to_timestamp = datetime.strptime(to_timestamp, format)
except ValueError:
raise NotFound(detail="Please provide to_timestamp in the format: "+format)
return Response({
'error': 'to_timestamp is is not in the format YYYY-MM-DD'
})

if (to_timestamp - from_timestamp).days > 31:
return Response({
'error': 'timeperiod should be less than 31 days'
})

try:
grant = Grant.objects.get(pk=grant_pk)
except Exception:
return Response({
'error': 'unable to find grant id'
})

txn_serializer = TransactionsSerializer

contributions_queryset = Contribution.objects.prefetch_related(
'subscription__grant'
).filter(
subscription__grant=grant,
created_on__lte=to_timestamp,
created_on__gt=from_timestamp
)
all_contributions = Paginator(contributions_queryset, results_limit)

contributions_queryset = all_contributions.page(page)
transactions = txn_serializer(contributions_queryset, many=True).data

clr_serializer = CLRPayoutsSerializer
clr_payouts = clr_serializer(CLRMatch.objects.filter(grant=grant), many=True).data

return Response({
'transactions': transactions,
'clr_payouts': clr_payouts,
'metadata' : {
'grant_name': grant.title,
'from_timestamp': from_timestamp,
'to_timestamp': to_timestamp,
'count': all_contributions.count,
'current_page': page,
'num_pages': all_contributions.num_pages,
'has_next': all_contributions.page(page).has_next()
}

})


@action(detail=False)
@ratelimit(key='ip', rate='5/s')
def contributions_sent_report(self, request):
"""
Generate report for grant contributions made by an address
URL: api/v0.1/grants/contributions_sent_report/?address=<address>&format=json
"""

donor_serializer = DonorSerializer
results_limit = 30

# Validate input pararms
page = self.request.query_params.get('page', '1')
if not page or not page.isdigit():
page = 1

address = self.request.query_params.get('address', None)
if not address:
return Response({
'error': 'address is a mandatory parameter'
})

now = datetime.now()
to_timestamp = self.request.query_params.get('to_timestamp')
from_timestamp = self.request.query_params.get('from_timestamp')

# Check timestamp
if not from_timestamp:
from_timestamp = now - timedelta(days=30)
else:
to_timestamp = datetime.now()
grants_queryset = grants_queryset.filter(subscriptions__subscription_contribution__created_on__lte=to_timestamp)
grants_queryset = grants_queryset.distinct()
try:
from_timestamp = datetime.strptime(from_timestamp, format)
except ValueError:
return Response({
'error': 'from_timestamp is not in format YYYY-MM-DD'
})

if not to_timestamp:
to_timestamp = now
else:
try:
to_timestamp = datetime.strptime(to_timestamp, format)
except ValueError:
return Response({
'error': 'to_timestamp is is not in the format YYYY-MM-DD'
})

if (to_timestamp - from_timestamp).days > 31:
return Response({
'error': 'timeperiod should be less than 31 days'
})

# Filter Contributions made by given address

contributions_queryset = Contribution.objects.prefetch_related('subscription').filter(subscription__contributor_address=address)

grantee_data = grantee_serializer(grants_queryset, many=True).data
donor_data = donor_serializer(contributions_queryset, many=True).data
all_contributions = Paginator(contributions_queryset, results_limit)
contributions_queryset = all_contributions.page(page)
data = donor_serializer(contributions_queryset, many=True).data

response = Response({
'grantee': grantee_data,
'donor': donor_data
'metadata' : {
'from': from_timestamp,
'to': to_timestamp,
'count': all_contributions.count,
'current_page': page,
'num_pages': all_contributions.num_pages,
'has_next': all_contributions.page(page).has_next()
},
'data': data,
})

return response
Expand Down
22 changes: 0 additions & 22 deletions app/grants/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,28 +96,6 @@ class Meta:

fields = ('amount', 'asset', 'usd_value', 'timestamp', 'round')

class GranteeSerializer(serializers.Serializer):
"""Handle serializing Grantee information."""

grant_name = serializers.CharField(source='title')
transactions = serializers.SerializerMethodField()
clr_payouts = serializers.SerializerMethodField()

def get_transactions(self, obj):
return TransactionsSerializer(
Contribution.objects.filter(subscription__grant__pk=obj.pk), many=True
).data

def get_clr_payouts(self, obj):
return CLRPayoutsSerializer(
CLRMatch.objects.filter(grant__pk=obj.pk), many=True
).data

class Meta:
"""Define the Grantee serializer metadata."""

fields = ('grant_name', 'transactions', 'clr_payout')

class DonorSerializer(serializers.Serializer):
"""Handle serializing Donor information."""

Expand Down

0 comments on commit 40ca895

Please sign in to comment.