Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
thalia
concrexit
Commits
bb8f1cf2
Unverified
Commit
bb8f1cf2
authored
Aug 14, 2016
by
Koen van Ingen
Committed by
Thom Wiggers
Sep 21, 2016
Browse files
Add statistics page, refs
#9
now feature equivalence with old website
parent
b6a420f2
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
website/members/static/members/js/highcharts.js
0 → 100644
View file @
bb8f1cf2
This diff is collapsed.
Click to expand it.
website/members/templates/members/statistics.html
0 → 100644
View file @
bb8f1cf2
{% 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 %}
website/members/tests.py
View file @
bb8f1cf2
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'
])
website/members/views.py
View file @
bb8f1cf2
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
website/thaliawebsite/menus.py
View file @
bb8f1cf2
...
...
@@ -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/'
},
]},
...
...
website/thaliawebsite/urls.py
View file @
bb8f1cf2
...
...
@@ -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'
),
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment