Unverified Commit bb8f1cf2 authored by Koen van Ingen's avatar Koen van Ingen Committed by Thom Wiggers

Add statistics page, refs #9 now feature equivalence with old website

parent b6a420f2
This source diff could not be displayed because it is too large. You can view the blob instead.
{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% block title %}{%trans "Statistics" %} - {{ block.super }}{% endblock %}
{% block body %}
<h1>{% trans "Statistics" %}</h1>
<h2>{% trans "Total amount of Thalia members" %}: {{ total_members }}</h2>
<script src="{% static 'members/js/highcharts.js' %}"></script>
<script>
var pieOptions = {
allowPointSlect: true,
cursor: 'pointer',
dataLabels : {
enabled: true,
formatter:function() { // Omit zero values
if(this.y != 0) {
return this.point.name + ": " + this.y; // TODO: add newline <br /> ?
}
},
distance: -50
}
};
$(function () {
Highcharts.theme = {
colors: ['#AE0046', '#E62272', '#E6478A', '#CC2482', '#8E1056', '#DC3472'],
title: {
style: {
color: '#000',
font: 'bold 16px "Trebuchet MS", Verdana, sans-serif'
}
},
subtitle: {
style: {
color: '#666666',
font: 'bold 12px "Trebuchet MS", Verdana, sans-serif'
}
},
credits: false, // TODO: are we allowed to remove credits without a license?
legend: {
itemStyle: {
font: '9pt Trebuchet MS, Verdana, sans-serif',
color: 'black'
},
itemHoverStyle: {
color: 'gray'
}
}
};
// Apply the theme
Highcharts.setOptions(Highcharts.theme);
$('#membersTypeChart').highcharts({
chart: {
type: 'pie'
},
title: {
text: '{% trans "Members per member type" %}'
},
plotOptions: {
pie: pieOptions
},
series: [{
name: 'Thalianen',
colorByPoint: true,
data : [{
name: '{% trans "Members" %}',
y: {{ total_stats_member_type.member }}
},{
name: '{% trans "Supporters" %}',
y: {{ total_stats_member_type.supporter }}
},{
name: '{% trans "Honorary members" %}',
y: {{ total_stats_member_type.honorary }}
}]
}]
});
$('#totalYearChart').highcharts({
chart: {
type: 'pie'
},
title: {
text: '{% trans "Total members per year" %}'
},
plotOptions: {
pie: pieOptions
},
series: [{
name: 'Thalianen',
colorByPoint: true,
data : [{
name: '{% trans "1st year" %}',
y: {{ total_stats_year.0.member|add:total_stats_year.0.supporter|add:total_stats_year.0.honorary }}
},{
name: '{% trans "2nd year" %}',
y: {{ total_stats_year.1.member|add:total_stats_year.1.supporter|add:total_stats_year.1.honorary }}
},{
name: '{% trans "3rd year" %}',
y: {{ total_stats_year.2.member|add:total_stats_year.2.supporter|add:total_stats_year.2.honorary }}
},{
name: '{% trans "4th year" %}',
y: {{ total_stats_year.3.member|add:total_stats_year.3.supporter|add:total_stats_year.3.honorary }}
},{
name: '{% trans "5th year" %}',
y: {{ total_stats_year.4.member|add:total_stats_year.4.supporter|add:total_stats_year.4.honorary }}
},{
name: '{% trans ">5th year" %}',
y: {{ total_stats_year.5.member|add:total_stats_year.5.supporter|add:total_stats_year.5.honorary }}
}]
}]
});
$('#membersYearChart').highcharts({
chart: {
type: 'pie'
},
title: {
text: '{% trans "Members per year" %}'
},
plotOptions: {
pie: pieOptions
},
series: [{
name: 'Thalianen',
colorByPoint: true,
data : [{
name: '{% trans "1st year" %}',
y: {{ total_stats_year.0.member }}
},{
name: '{% trans "2nd year" %}',
y: {{ total_stats_year.1.member }}
},{
name: '{% trans "3rd year" %}',
y: {{ total_stats_year.2.member }}
},{
name: '{% trans "4th year" %}',
y: {{ total_stats_year.3.member }}
},{
name: '{% trans "5th year" %}',
y: {{ total_stats_year.4.member }}
},{
name: '{% trans ">5th year" %}',
y: {{ total_stats_year.5.member }}
}]
}]
});
$('#supportersYearChart').highcharts({
chart: {
type: 'pie'
},
title: {
text: '{% trans "Supporters per year" %}'
},
plotOptions: {
pie: pieOptions
},
series: [{
name: 'Thalianen',
colorByPoint: true,
data : [{
name: '{% trans "1st year" %}',
y: {{ total_stats_year.0.supporter }}
},{
name: '{% trans "2nd year" %}',
y: {{ total_stats_year.1.supporter }}
},{
name: '{% trans "3rd year" %}',
y: {{ total_stats_year.2.supporter }}
},{
name: '{% trans "4th year" %}',
y: {{ total_stats_year.3.supporter }}
},{
name: '{% trans "5th year" %}',
y: {{ total_stats_year.4.supporter }}
},{
name: '{% trans ">5th year" %}',
y: {{ total_stats_year.5.supporter }}
}]
}]
});
});
</script>
<div id="membersTypeChart" style="width:100%; height:400px;"></div>
<div id="totalYearChart" style="width:100%; height:400px;"></div>
<div id="membersYearChart" style="width:100%; height:400px;"></div>
<div id="supportersYearChart" style="width:100%; height:400px;"></div>
<br/><br/>
{% endblock %}
from datetime import datetime
from datetime import datetime, date, timedelta
from django.test import TestCase
from django.utils import timezone
from django.contrib.auth.models import User
from members.models import Member
from members.models import Member, Membership
from members.views import gen_stats_year, gen_stats_member_type
class MemberBirthdayTest(TestCase):
......@@ -54,3 +56,155 @@ class MemberBirthdayTest(TestCase):
def test_person_born_in_range_spanning_multiple_years(self):
self._assert_thom('1992-12-31', '1995-01-01')
class StatisticsTest(TestCase):
def setUp(self):
# Add 10 members with default membership
for i in range(10):
u = User(username=i)
u.save()
membership = Membership(user=u, type="member")
membership.save()
m = Member(user=u)
m.save()
def sum_members(self, members, type=None):
s = 0
for i in members:
if type is None:
for j in i.values():
s = s + j
else:
s = s + i[type]
return s
def sum_member_types(self, members):
s = 0
for i in members.values():
s = s + i
return s
def test_gen_stats_year_no_members(self):
member_types = ["member", "supporter", "honorary"]
result = gen_stats_year(member_types)
self.assertEqual(0, self.sum_members(result))
def test_gen_stats_active(self):
"""
Testing if active and non-active objects are counted correctly
"""
member_types = ["member", "supporter", "honorary"]
current_year = date.today().year
# Set start date to current year - 1:
for m in Member.objects.all():
m.starting_year = current_year - 1
m.save()
result = gen_stats_year(member_types)
self.assertEqual(10, self.sum_members(result))
self.assertEqual(10, self.sum_members(result, "member"))
result = gen_stats_member_type(member_types)
self.assertEqual(10, self.sum_member_types(result))
# Change one membership to supporter should decrease amount of members
m = Membership.objects.all()[0]
m.type = "supporter"
m.save()
result = gen_stats_year(member_types)
self.assertEqual(10, self.sum_members(result))
self.assertEqual(9, self.sum_members(result, "member"))
self.assertEqual(1, self.sum_members(result, "supporter"))
result = gen_stats_member_type(member_types)
self.assertEqual(10, self.sum_member_types(result))
self.assertEqual(9, result["member"])
self.assertEqual(1, result["supporter"])
# Same for honorary members
m = Membership.objects.all()[1]
m.type = "honorary"
m.save()
result = gen_stats_year(member_types)
self.assertEqual(10, self.sum_members(result))
self.assertEqual(8, self.sum_members(result, "member"))
self.assertEqual(1, self.sum_members(result, "supporter"))
self.assertEqual(1, self.sum_members(result, "honorary"))
result = gen_stats_member_type(member_types)
self.assertEqual(10, self.sum_member_types(result))
self.assertEqual(8, result["member"])
self.assertEqual(1, result["supporter"])
self.assertEqual(1, result["honorary"])
# Terminate one membership by setting end date to current_year -1,
# should decrease total amount and total members
m = Membership.objects.all()[2]
m.until = timezone.now() - timedelta(days=365)
m.save()
result = gen_stats_year(member_types)
self.assertEqual(9, self.sum_members(result))
self.assertEqual(7, self.sum_members(result, "member"))
self.assertEqual(1, self.sum_members(result, "supporter"))
self.assertEqual(1, self.sum_members(result, "honorary"))
result = gen_stats_member_type(member_types)
self.assertEqual(9, self.sum_member_types(result))
self.assertEqual(7, result["member"])
self.assertEqual(1, result["supporter"])
self.assertEqual(1, result["honorary"])
def test_gen_stats_different_years(self):
member_types = ["member", "supporter", "honorary"]
current_year = date.today().year
# one first year student
m = Member.objects.all()[0]
m.starting_year = current_year
m.save()
# one second year student
m = Member.objects.all()[1]
m.starting_year = current_year - 1
m.save()
# no third year students
# one fourth year student
m = Member.objects.all()[2]
m.starting_year = current_year - 3
m.save()
# no fifth year students
# one >5 year student
m = Member.objects.all()[3]
m.starting_year = current_year - 5
m.save()
# 4 active members
result = gen_stats_year(member_types)
self.assertEqual(4, self.sum_members(result))
self.assertEqual(4, self.sum_members(result, "member"))
# one first year student
self.assertEqual(1, result[0]['member'])
# one second year student
self.assertEqual(1, result[1]['member'])
# no third year students
self.assertEqual(0, result[2]['member'])
# one fourth year student
self.assertEqual(1, result[3]['member'])
# no fifth year students
self.assertEqual(0, result[4]['member'])
# one >5 year student
self.assertEqual(1, result[5]['member'])
import os
from datetime import date
from sendfile import sendfile
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import get_object_or_404, render
from django.contrib.auth.decorators import login_required
from django.utils.text import slugify
from sendfile import sendfile
from .models import BecomeAMemberDocument
from .models import Member
from .models import BecomeAMemberDocument, Member
from .forms import MemberForm
......@@ -160,3 +159,60 @@ def get_become_a_member_document(request, pk):
ext = os.path.splitext(document.file.path)[1]
return sendfile(request, document.file.path, attachment=True,
attachment_filename=slugify(document.name) + ext)
def statistics(request):
member_types = ["member", "supporter", "honorary"]
# The numbers
total = Member.active_members.count()
context = {
"total_members": total,
"total_stats_year": gen_stats_year(member_types),
"total_stats_member_type": gen_stats_member_type(member_types)
}
return render(request, 'members/statistics.html', context)
def gen_stats_member_type(member_types):
total = dict()
for member_type in member_types:
total[member_type] = (Member
.active_members
.filter(user__membership__type=member_type)
.count())
return total
def gen_stats_year(member_types):
"""
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
students.
"""
stats_year = []
current_year = date.today().year
for i in range(5):
new = dict()
for member_type in member_types:
new[member_type] = (Member
.active_members
.filter(starting_year=current_year - i)
.filter(user__membership__type=member_type)
.count())
stats_year.append(new)
# Add multi year members
new = dict()
for member_type in member_types:
new[member_type] = (Member
.active_members
.filter(starting_year__lt=current_year - 4)
.filter(user__membership__type=member_type)
.count())
stats_year.append(new)
return stats_year
......@@ -15,7 +15,7 @@ main = [
{'title': _('For Members'), 'name': 'for-members', 'authenticated': True,
'submenu': [
{'title': _('Photos'), 'name': 'photos:index'},
{'title': _('Statistics'), 'name': '#'},
{'title': _('Statistics'), 'name': 'statistics'},
{'title': _('Become Active'), 'name': 'become-active'},
{'title': _('Wiki'), 'url': '/wiki/'},
]},
......
......@@ -65,6 +65,7 @@ urlpatterns = [
url(r'^for-members/', include([
url(r'^become-active', TemplateView.as_view(template_name='singlepages/become_active.html'), name='become-active'),
url(r'^photos/', include('photos.urls', namespace='photos')),
url(r'^statistics/', members.views.statistics, name='statistics'),
])),
url(r'^career/', include('partners.urls', namespace='partners')),
url(r'^contact$', TemplateView.as_view(template_name='singlepages/contact.html'), name='contact'),
......
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