Commit 8d015ca5 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg

Change graph types on statistics page

parent 851af8bb
from django.db.models import Count, Q
from django.utils import timezone
from activemembers.models import Committee
def generate_statistics():
"""
Generate statistics about number of members in each committee
:return: Dict with key, value being resp. name, member count of committees.
"""
committees = Committee.active_objects.annotate(member_count=(
Count('members', filter=(
Q(membergroupmembership__until=None) |
Q(membergroupmembership__until__gte=timezone.now())))))
data = {}
for committee in committees:
data.update({
committee.name: committee.member_count
})
return data
from collections import OrderedDict from collections import OrderedDict
from django.utils import timezone from django.utils import timezone
from django.utils.datetime_safe import date
from django.utils.translation import ugettext_lazy as _, get_language from django.utils.translation import ugettext_lazy as _, get_language
from events import emails from events import emails
from events.exceptions import RegistrationError from events.exceptions import RegistrationError
from events.models import Registration, RegistrationInformationField from events.models import Registration, RegistrationInformationField, Event
from payments.models import Payment from payments.models import Payment
from utils.snippets import datetime_to_lectureyear
def is_user_registered(member, event): def is_user_registered(member, event):
...@@ -286,3 +288,24 @@ def update_registration_by_organiser(registration, member, data): ...@@ -286,3 +288,24 @@ def update_registration_by_organiser(registration, member, data):
registration.present = data['present'] registration.present = data['present']
registration.save() registration.save()
def generate_category_statistics():
"""
Generate statistics about events, number of events per category
:return: Dict with key, value resp. being category, event count.
"""
year = datetime_to_lectureyear(timezone.now())
data = {}
for i in range(5):
year_start = date(year=year - i, month=9, day=1)
year_end = date(year=year - i + 1, month=9, day=1)
data[str(year - i)] = {
str(display): Event.objects.filter(category=key,
start__gte=year_start,
end__lte=year_end).count()
for key, display in Event.EVENT_CATEGORIES
}
return data
...@@ -6,13 +6,15 @@ from django.db.models import Q, Count ...@@ -6,13 +6,15 @@ from django.db.models import Q, Count
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext from django.utils.translation import gettext
from activemembers.models import Committee
from events.models import Event
from members import emails from members import emails
from members.models import Membership, Member from members.models import Membership, Member
from utils.snippets import datetime_to_lectureyear from utils.snippets import datetime_to_lectureyear
def _member_group_memberships( def _member_group_memberships(
member: Member, condition: Callable[[Membership], bool] member: Member, condition: Callable[[Membership], bool]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Determines the group membership of a user based on a condition Determines the group membership of a user based on a condition
...@@ -34,7 +36,7 @@ def _member_group_memberships( ...@@ -34,7 +36,7 @@ def _member_group_memberships(
period['role'] = membership.role period['role'] = membership.role
if (membership.until is None and if (membership.until is None and
hasattr(membership.group, 'board')): hasattr(membership.group, 'board')):
period['until'] = membership.group.board.until period['until'] = membership.group.board.until
name = membership.group.name name = membership.group.name
...@@ -85,60 +87,58 @@ def member_societies(member) -> List: ...@@ -85,60 +87,58 @@ def member_societies(member) -> List:
return sorted(societies.values(), key=lambda x: x['earliest']) return sorted(societies.values(), key=lambda x: x['earliest'])
def gen_stats_member_type(member_types) -> Dict[str, int]: def gen_stats_member_type() -> Dict[str, int]:
""" """
Generate a dictionary where every key is a member type with Generate a dictionary where every key is a member type with
the value being the number of current members of that type the value being the number of current members of that type
""" """
total = dict()
for member_type in member_types: data = {}
total[member_type] = (Membership for key, display in Membership.MEMBERSHIP_TYPES:
data[str(display)] = (Membership
.objects .objects
.filter(since__lte=date.today()) .filter(since__lte=date.today())
.filter(Q(until__isnull=True) | .filter(Q(until__isnull=True) |
Q(until__gt=date.today())) Q(until__gt=date.today()))
.filter(type=member_type) .filter(type=key)
.count()) .count())
return total return data
def gen_stats_year( def gen_stats_year() -> Dict[str, Dict[str, int]]:
member_types) -> List[Dict[Union[str, Any], Union[int, Any]]]:
""" """
Generate list with 6 entries, where each entry represents the total amount Generate list with 6 entries, where each entry represents the total amount
of Thalia members in a year. The sixth element contains all the multi-year of Thalia members in a year. The sixth element contains all the multi-year
students. students.
""" """
stats_year = [] stats_year = {}
current_year = datetime_to_lectureyear(date.today()) current_year = datetime_to_lectureyear(date.today())
for i in range(5): for i in range(5):
new = dict() new = {}
new['cohort'] = current_year - i for key, _ in Membership.MEMBERSHIP_TYPES:
for member_type in member_types: new[key] = (
new[member_type] = (
Membership.objects Membership.objects
.filter(user__profile__starting_year=current_year - i) .filter(user__profile__starting_year=current_year - i)
.filter(since__lte=date.today()) .filter(since__lte=date.today())
.filter(Q(until__isnull=True) | .filter(Q(until__isnull=True) |
Q(until__gt=date.today())) Q(until__gt=date.today()))
.filter(type=member_type) .filter(type=key)
.count()) .count())
stats_year.append(new) stats_year[str(current_year - i)] = new
# Add multi year members # Add multi year members
new = dict() new = {}
new['cohort'] = gettext('Older') for key, _ in Membership.MEMBERSHIP_TYPES:
for member_type in member_types: new[key] = (
new[member_type] = (
Membership.objects Membership.objects
.filter(user__profile__starting_year__lt=current_year - 4) .filter(user__profile__starting_year__lt=current_year - 4)
.filter(since__lte=date.today()) .filter(since__lte=date.today())
.filter(Q(until__isnull=True) | .filter(Q(until__isnull=True) |
Q(until__gt=date.today())) Q(until__gt=date.today()))
.filter(type=member_type) .filter(type=key)
.count()) .count())
stats_year.append(new) stats_year[str(gettext('Older'))] = new
return stats_year return stats_year
...@@ -205,7 +205,7 @@ def execute_data_minimisation(dry_run=False, members=None) -> List[Member]: ...@@ -205,7 +205,7 @@ def execute_data_minimisation(dry_run=False, members=None) -> List[Member]:
processed_members = [] processed_members = []
for member in members: for member in members:
if (member.latest_membership is None or if (member.latest_membership is None or
member.latest_membership.until <= deletion_period): member.latest_membership.until <= deletion_period):
processed_members.append(member) processed_members.append(member)
profile = member.profile profile = member.profile
profile.student_number = None profile.student_number = None
......
...@@ -12,18 +12,25 @@ ...@@ -12,18 +12,25 @@
<h2 class="text-center mb-4">{% trans "Total amount of Thalia members" %}: {{ total_members }}</h2> <h2 class="text-center mb-4">{% trans "Total amount of Thalia members" %}: {{ total_members }}</h2>
<div class="row"> <div class="row">
<div class="col-sm-6 col-lg-4"> <div class="col-lg-6">
<canvas id="members-type-chart"></canvas> <canvas id="members-type-chart"></canvas>
</div> </div>
<div class="col-sm-6 col-lg-4"> <div class="col-lg-6">
<canvas id="total-year-chart"></canvas> <canvas id="total-year-chart"></canvas>
</div> </div>
<div class="col-sm-6 col-lg-4">
<canvas id="members-year-chart"></canvas> <div class="col-12 mt-2 mb-2">
<canvas id="committees-members-chart"></canvas>
</div> </div>
<div class="col-12 mb-2">
<canvas id="event-category-chart"></canvas>
</div>
<div class="col-sm-6 col-lg-4"> <div class="col-sm-6 col-lg-4">
<canvas id="benefactors-year-chart"></canvas> <canvas id="pizza-total-type-chart"></canvas>
</div> </div>
<div class="col-sm-6 col-lg-4"> <div class="col-sm-6 col-lg-4">
<canvas id="pizza-total-type-chart"></canvas> <canvas id="pizza-total-type-chart"></canvas>
</div> </div>
......
...@@ -35,26 +35,24 @@ class StatisticsTest(TestCase): ...@@ -35,26 +35,24 @@ class StatisticsTest(TestCase):
return sum(members.values()) return sum(members.values())
def test_gen_stats_year_no_members(self): def test_gen_stats_year_no_members(self):
member_types = [t[0] for t in Membership.MEMBERSHIP_TYPES] result = gen_stats_year()
result = gen_stats_year(member_types)
self.assertEqual(0, self.sum_members(result)) self.assertEqual(0, self.sum_members(result))
def test_gen_stats_active(self): def test_gen_stats_active(self):
""" """
Testing if active and non-active objects are counted correctly Testing if active and non-active objects are counted correctly
""" """
member_types = [t[0] for t in Membership.MEMBERSHIP_TYPES]
current_year = datetime_to_lectureyear(date.today()) current_year = datetime_to_lectureyear(date.today())
# Set start date to current year - 1: # Set start date to current year - 1:
for m in Member.objects.all(): for m in Member.objects.all():
m.profile.starting_year = current_year - 1 m.profile.starting_year = current_year - 1
m.profile.save() m.profile.save()
result = gen_stats_year(member_types) result = gen_stats_year()
self.assertEqual(10, self.sum_members(result)) self.assertEqual(10, self.sum_members(result))
self.assertEqual(10, self.sum_members(result, Membership.MEMBER)) self.assertEqual(10, self.sum_members(result, Membership.MEMBER))
result = gen_stats_member_type(member_types) result = gen_stats_member_type()
self.assertEqual(10, self.sum_member_types(result)) self.assertEqual(10, self.sum_member_types(result))
# Change one membership to benefactor should decrease amount of members # Change one membership to benefactor should decrease amount of members
...@@ -62,12 +60,12 @@ class StatisticsTest(TestCase): ...@@ -62,12 +60,12 @@ class StatisticsTest(TestCase):
m.type = Membership.BENEFACTOR m.type = Membership.BENEFACTOR
m.save() m.save()
result = gen_stats_year(member_types) result = gen_stats_year()
self.assertEqual(10, self.sum_members(result)) self.assertEqual(10, self.sum_members(result))
self.assertEqual(9, self.sum_members(result, Membership.MEMBER)) self.assertEqual(9, self.sum_members(result, Membership.MEMBER))
self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR)) self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR))
result = gen_stats_member_type(member_types) result = gen_stats_member_type()
self.assertEqual(10, self.sum_member_types(result)) self.assertEqual(10, self.sum_member_types(result))
self.assertEqual(9, result[Membership.MEMBER]) self.assertEqual(9, result[Membership.MEMBER])
self.assertEqual(1, result[Membership.BENEFACTOR]) self.assertEqual(1, result[Membership.BENEFACTOR])
...@@ -77,13 +75,13 @@ class StatisticsTest(TestCase): ...@@ -77,13 +75,13 @@ class StatisticsTest(TestCase):
m.type = Membership.HONORARY m.type = Membership.HONORARY
m.save() m.save()
result = gen_stats_year(member_types) result = gen_stats_year()
self.assertEqual(10, self.sum_members(result)) self.assertEqual(10, self.sum_members(result))
self.assertEqual(8, self.sum_members(result, Membership.MEMBER)) self.assertEqual(8, self.sum_members(result, Membership.MEMBER))
self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR)) self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR))
self.assertEqual(1, self.sum_members(result, Membership.HONORARY)) self.assertEqual(1, self.sum_members(result, Membership.HONORARY))
result = gen_stats_member_type(member_types) result = gen_stats_member_type()
self.assertEqual(10, self.sum_member_types(result)) self.assertEqual(10, self.sum_member_types(result))
self.assertEqual(8, result[Membership.MEMBER]) self.assertEqual(8, result[Membership.MEMBER])
self.assertEqual(1, result[Membership.BENEFACTOR]) self.assertEqual(1, result[Membership.BENEFACTOR])
...@@ -94,13 +92,13 @@ class StatisticsTest(TestCase): ...@@ -94,13 +92,13 @@ class StatisticsTest(TestCase):
m = Membership.objects.all()[2] m = Membership.objects.all()[2]
m.until = timezone.now() - timedelta(days=365) m.until = timezone.now() - timedelta(days=365)
m.save() m.save()
result = gen_stats_year(member_types) result = gen_stats_year()
self.assertEqual(9, self.sum_members(result)) self.assertEqual(9, self.sum_members(result))
self.assertEqual(7, self.sum_members(result, Membership.MEMBER)) self.assertEqual(7, self.sum_members(result, Membership.MEMBER))
self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR)) self.assertEqual(1, self.sum_members(result, Membership.BENEFACTOR))
self.assertEqual(1, self.sum_members(result, Membership.HONORARY)) self.assertEqual(1, self.sum_members(result, Membership.HONORARY))
result = gen_stats_member_type(member_types) result = gen_stats_member_type()
self.assertEqual(9, self.sum_member_types(result)) self.assertEqual(9, self.sum_member_types(result))
self.assertEqual(7, result[Membership.MEMBER]) self.assertEqual(7, result[Membership.MEMBER])
self.assertEqual(1, result[Membership.BENEFACTOR]) self.assertEqual(1, result[Membership.BENEFACTOR])
...@@ -138,7 +136,7 @@ class StatisticsTest(TestCase): ...@@ -138,7 +136,7 @@ class StatisticsTest(TestCase):
m.profile.save() m.profile.save()
# 4 active members # 4 active members
result = gen_stats_year(member_types) result = gen_stats_year()
self.assertEqual(4, self.sum_members(result)) self.assertEqual(4, self.sum_members(result))
self.assertEqual(4, self.sum_members(result, Membership.MEMBER)) self.assertEqual(4, self.sum_members(result, Membership.MEMBER))
......
...@@ -27,6 +27,8 @@ from . import models ...@@ -27,6 +27,8 @@ from . import models
from .forms import ProfileForm from .forms import ProfileForm
from .services import member_achievements from .services import member_achievements
from .services import member_societies from .services import member_societies
import events.services as event_services
import activemembers.services as activemembers_services
class ObtainThaliaAuthToken(ObtainAuthToken): class ObtainThaliaAuthToken(ObtainAuthToken):
...@@ -219,18 +221,23 @@ class StatisticsView(TemplateView): ...@@ -219,18 +221,23 @@ class StatisticsView(TemplateView):
def get_context_data(self, **kwargs) -> dict: def get_context_data(self, **kwargs) -> dict:
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
member_types = [t[0] for t in Membership.MEMBERSHIP_TYPES]
total = models.Member.current_members.count() total = models.Member.current_members.count()
context.update({ context.update({
"total_members": total, "total_members": total,
"statistics": json.dumps({ "statistics": json.dumps({
"cohort_sizes": services.gen_stats_year(member_types), "cohort_sizes":
services.gen_stats_year(),
"member_type_distribution": "member_type_distribution":
services.gen_stats_member_type(member_types), services.gen_stats_member_type(),
"total_pizza_orders": pizzas.services.gen_stats_pizza_orders(), "total_pizza_orders":
pizzas.services.gen_stats_pizza_orders(),
"current_pizza_orders": "current_pizza_orders":
pizzas.services.gen_stats_current_pizza_orders(), pizzas.services.gen_stats_current_pizza_orders(),
"committee_sizes":
activemembers_services.generate_statistics(),
"event_categories":
event_services.generate_category_statistics(),
}) })
}) })
......
import operator
from events.services import is_organiser from events.services import is_organiser
from . models import Product, Order, PizzaEvent from . models import Product, Order, PizzaEvent
def gen_stats_pizza_orders(): def gen_stats_pizza_orders():
total = [] total = {}
for product in Product.objects.all(): for product in Product.objects.all():
total.append({ total.update({
'name': product.name, product.name: Order.objects.filter(product=product).count(),
'total': Order.objects.filter(product=product).count(),
}) })
total.sort(key=lambda prod: prod['total'], reverse=True) return {
i[0]: i[1]
return total for i in sorted(total.items(), key=lambda x: x[1], reverse=True)[:5]
if i[1] > 0
}
def gen_stats_current_pizza_orders(): def gen_stats_current_pizza_orders():
total = [] total = {}
current_pizza_event = PizzaEvent.current() current_pizza_event = PizzaEvent.current()
if not current_pizza_event: if not current_pizza_event:
return None return None
for product in Product.objects.filter(): for product in Product.objects.filter():
total.append({ total.update({
'name': product.name, product.name: Order.objects.filter(
'total': Order.objects.filter(
product=product, product=product,
pizza_event=current_pizza_event, pizza_event=current_pizza_event,
).count(), ).count(),
}) })
total.sort(key=lambda prod: prod['total'], reverse=True) return {
i[0]: i[1]
return total for i in sorted(total.items(), key=lambda x: x[1], reverse=True)[:5]
if i[1] > 0
}
def can_change_order(member, pizza_event): def can_change_order(member, pizza_event):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment