Skip to content

Commit

Permalink
Merge pull request #5222 from onepercentclub/release/ytd-stats
Browse files Browse the repository at this point in the history
Feature: Add year filter to homepage stats
  • Loading branch information
gannetson committed Aug 31, 2022
2 parents 9a58a29 + 8d5ba26 commit 180f170
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 41 deletions.
1 change: 1 addition & 0 deletions bluebottle/activities/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ class ActivityAdmin(PolymorphicParentModelAdmin, StateMachineAdmin):

def lookup_allowed(self, key, value):
if key in [
'goals__type__id__exact',
'initiative__location__id__exact',
'initiative__location__subregion__id__exact',
'initiative__location__subregion__region__id__exact',
Expand Down
18 changes: 18 additions & 0 deletions bluebottle/cms/migrations/0069_statscontent_year.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2022-08-19 08:19

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cms', '0068_auto_20220121_1448'),
]

operations = [
migrations.AddField(
model_name='statscontent',
name='year',
field=models.IntegerField(blank=True, null=True),
),
]
18 changes: 18 additions & 0 deletions bluebottle/cms/migrations/0070_homepagestatisticscontent_year.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2022-08-19 08:30

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cms', '0069_statscontent_year'),
]

operations = [
migrations.AddField(
model_name='homepagestatisticscontent',
name='year',
field=models.IntegerField(blank=True, null=True),
),
]
2 changes: 2 additions & 0 deletions bluebottle/cms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ def items(self):
class StatsContent(TitledContent):
type = 'statistics'
preview_template = 'admin/cms/preview/stats.html'
year = models.IntegerField(blank=True, null=True)

class Meta:
verbose_name = _('Platform Statistics')
Expand All @@ -234,6 +235,7 @@ def __str__(self):
class HomepageStatisticsContent(TitledContent):
type = 'homepage-statistics'
preview_template = 'admin/cms/preview/homepage-statistics.html'
year = models.IntegerField(blank=True, null=True)

class Meta:
verbose_name = _('Statistics')
Expand Down
3 changes: 2 additions & 1 deletion bluebottle/cms/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,15 @@ class Meta(object):
class HomepageStatisticsContentSerializer(serializers.ModelSerializer):
title = serializers.CharField()
sub_title = serializers.CharField()
year = serializers.IntegerField()
count = serializers.SerializerMethodField()

def get_count(self, obj):
return len(BaseStatistic.objects.filter(active=True))

class Meta(object):
model = HomepageStatisticsContent
fields = ('id', 'type', 'title', 'sub_title', 'count')
fields = ('id', 'type', 'title', 'sub_title', 'year', 'count')


class QuoteSerializer(serializers.ModelSerializer):
Expand Down
4 changes: 2 additions & 2 deletions bluebottle/cms/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ def test_results_stats(self):
DonorFactory.create(
activity=funding,
status='succeeded',
contributor_date=yesterday,
created=yesterday,
user=user,
amount=Money(50, 'EUR')
)
DonorFactory.create(
activity=funding,
status='succeeded',
contributor_date=long_ago,
created=long_ago,
user=user,
amount=Money(50, 'EUR')
)
Expand Down
12 changes: 10 additions & 2 deletions bluebottle/impact/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _

from parler.admin import TranslatableAdmin
Expand All @@ -18,16 +20,22 @@ def unit(self, obj):


class ImpactTypeAdmin(TranslatableAdmin):
list_display = admin.ModelAdmin.list_display + ('name', 'active')
list_display = admin.ModelAdmin.list_display + ('name', 'active', 'activities')

def get_prepopulated_fields(self, request, obj=None):
return {'slug': ('name',)}
readonly_fields = ('activities',)

fields = (
'name', 'slug', 'unit', 'active',
'icon', 'text', 'text_with_target',
'text_passed',
'text_passed', 'activities'
)

def activities(self, obj):
url = reverse('admin:activities_activity_changelist')
total = obj.goals.count()
return format_html('<a href="{}?goals__type__id__exact={}">{} activities</a>', url, obj.id, total)


admin.site.register(ImpactType, ImpactTypeAdmin)
17 changes: 12 additions & 5 deletions bluebottle/statistics/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from builtins import str
from builtins import object
from builtins import str

from adminsortable.models import SortableMixin
from django.db import models
from django.db.models import Sum
Expand Down Expand Up @@ -117,9 +118,6 @@ def icon(self):
'time_activities_succeeded': 'event-completed',
'deed_succeeded': 'deed-completed',
'fundings_succeeded': 'funding-completed',

'participants': 'people',

'fundings_online': 'funding',
'time_activities_online': 'event',
'deeds_activities_online': 'deed',
Expand All @@ -140,7 +138,7 @@ def icon(self):
return mapping.get(self.query)

def get_value(self, start=None, end=None):
return getattr(Statistics(), self.query)
return getattr(Statistics(start, end), self.query)

def __str__(self):
return str(self.query)
Expand All @@ -157,6 +155,15 @@ class ImpactStatistic(BaseStatistic):
impact_type = models.ForeignKey('impact.ImpactType', on_delete=models.CASCADE)

def get_value(self, start=None, end=None):
if start and end:
return self.impact_type.goals.filter(
activity__status='succeeded',
activity__created__gte=start,
activity__created__lt=end,
).aggregate(
sum=Sum('realized')
)['sum'] or 0

return self.impact_type.goals.filter(
activity__status='succeeded',
).aggregate(
Expand Down
13 changes: 13 additions & 0 deletions bluebottle/statistics/renderers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django_tools.middlewares.ThreadLocal import get_current_request
from rest_framework_json_api.renderers import JSONRenderer


class StatisticsRenderer(JSONRenderer):

@classmethod
def build_json_resource_obj(cls, *args, **kwargs):
obj = super(StatisticsRenderer, cls).build_json_resource_obj(*args, **kwargs)
req = get_current_request()
if 'year' in req.GET:
obj['id'] = obj['id'] + '-' + req.GET['year']
return obj
26 changes: 19 additions & 7 deletions bluebottle/statistics/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import datetime
from builtins import str
from builtins import object

from django.utils.timezone import get_current_timezone
from rest_framework import serializers
from rest_framework_json_api.serializers import PolymorphicModelSerializer, ModelSerializer

Expand All @@ -8,11 +11,21 @@
)


tz = get_current_timezone()


class BaseStatisticSerializer(ModelSerializer):
value = serializers.SerializerMethodField()

def get_value(self, obj):
value = obj.get_value()
params = self.context['request'].query_params
if 'year' in params:
year = int(params['year'])
start = datetime.datetime(year, 1, 1, tzinfo=tz)
end = datetime.datetime(year, 12, 31, tzinfo=tz)
value = obj.get_value(start, end)
else:
value = obj.get_value()

try:
return {
Expand All @@ -22,23 +35,21 @@ def get_value(self, obj):
except AttributeError:
return value

return value


class ManualStatisticSerializer(BaseStatisticSerializer):
class Meta(object):
model = ManualStatistic
fields = ('id', 'value', 'name', 'icon')
fields = ('value', 'name', 'icon', 'sequence')

class JSONAPIMeta(object):
resource_name = 'statistics/manual-statistics'
fields = ('id', 'value', 'name', 'icon', )


class DatabaseStatisticSerializer(BaseStatisticSerializer):

class Meta(object):
model = DatabaseStatistic
fields = ('id', 'value', 'name', 'query', 'icon', )
fields = ('value', 'name', 'query', 'icon', 'sequence')

class JSONAPIMeta(object):
resource_name = 'statistics/database-statistics'
Expand All @@ -51,14 +62,15 @@ class ImpactStatisticSerializer(BaseStatisticSerializer):

class Meta(object):
model = ImpactStatistic
fields = ('id', 'value', 'impact_type')
fields = ('value', 'impact_type', 'sequence')

class JSONAPIMeta(object):
resource_name = 'statistics/impact-statistics'
included_resources = ['impact_type']


class StatisticSerializer(PolymorphicModelSerializer):

polymorphic_serializers = [
DatabaseStatisticSerializer,
ManualStatisticSerializer,
Expand Down
26 changes: 11 additions & 15 deletions bluebottle/statistics/statistics.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
from builtins import object
from django.db.models import Q, Count
from django.db.models.aggregates import Sum

from django.contrib.contenttypes.models import ContentType

from django.db.models import Q, Count
from django.db.models.aggregates import Sum
from memoize import memoize

from moneyed.classes import Money

from bluebottle.activities.models import Contributor, Activity, EffortContribution
from bluebottle.clients import properties

from bluebottle.deeds.models import Deed, DeedParticipant
from bluebottle.funding.models import Donor, Funding
from bluebottle.funding_pledge.models import PledgePayment
from bluebottle.initiatives.models import Initiative
from bluebottle.activities.models import Contributor, Activity, EffortContribution
from bluebottle.members.models import Member
from bluebottle.time_based.models import (
DateActivity,
PeriodActivity,
TimeContribution
)
from bluebottle.funding.models import Donor, Funding
from bluebottle.deeds.models import Deed, DeedParticipant
from bluebottle.funding_pledge.models import PledgePayment
from bluebottle.utils.exchange_rates import convert


Expand Down Expand Up @@ -121,7 +118,7 @@ def fundings_succeeded(self):
def deeds_succeeded(self):
""" Total number of succeeded tasks """
return len(Deed.objects.filter(
self.date_filter('slots__start'),
self.date_filter('start'),
status='succeeded'
))

Expand Down Expand Up @@ -212,12 +209,11 @@ def activities_online(self):
return len(date_activities) + len(funding_activities) + len(period_activities) + len(deed_activities)

@property
@memoize(timeout=timeout)
def donated_total(self):
""" Total amount donated to all activities"""
donations = Donor.objects.filter(
self.date_filter('contributor_date'),
status='succeeded'
self.date_filter('created'),
status='succeeded',
)
totals = donations.order_by('amount_currency').values('amount_currency').annotate(total=Sum('amount'))
amounts = [Money(total['total'], total['amount_currency']) for total in totals]
Expand Down Expand Up @@ -308,7 +304,7 @@ def participants(self):
def pledged_total(self):
""" Total amount of pledged donations """
donations = PledgePayment.objects.filter(
self.date_filter('donation__contributor_date'),
self.date_filter('created'),
donation__status='succeeded'
)
totals = donations.values(
Expand All @@ -328,7 +324,7 @@ def pledged_total(self):
def members(self):
""" Total amount of members."""
members = Member.objects.filter(
self.date_filter('created'),
self.date_filter('date_joined'),
is_active=True
)
return len(members)
Expand Down
Loading

0 comments on commit 180f170

Please sign in to comment.