Skip to content

Commit 563d74f

Browse files
authored
Show First time attendee stats (#297)
* Show First time attendee stats Display number of first time attendee and the percentage Closes #296
1 parent 136a414 commit 563d74f

File tree

5 files changed

+130
-2
lines changed

5 files changed

+130
-2
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 5.2.8 on 2025-12-05 00:11
2+
3+
from django.db import migrations, models
4+
5+
import portal.models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("attendee", "0002_attendeeprofile"),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name="attendeeprofile",
17+
name="participated_in_previous_event",
18+
field=portal.models.ChoiceArrayField(
19+
base_field=models.CharField(
20+
blank=True,
21+
choices=[
22+
("PyLadiesCon 2024", "PyLadiesCon 2024"),
23+
("PyLadiesCon 2023", "PyLadiesCon 2023"),
24+
("No this is my first one", "No this is my first one"),
25+
],
26+
help_text="Have you participated in a previous event?",
27+
null=True,
28+
),
29+
default=list,
30+
size=None,
31+
),
32+
),
33+
]

attendee/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class PretixOrderstatus(StrEnum):
3434
PARTICIPATED_IN_PREVIOUS_EVENT_CHOICES = [
3535
("PyLadiesCon 2024", "PyLadiesCon 2024"),
3636
("PyLadiesCon 2023", "PyLadiesCon 2023"),
37-
("No, this is my first one", "No, this is my first one"),
37+
("No this is my first one", "No this is my first one"),
3838
]
3939
CURRENT_POSITION_CHOICES = [
4040
("Engineer", "Engineer"),

portal/common.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
from django.core.cache import cache
22
from django.db.models import Count, Sum
33

4-
from attendee.models import AttendeeProfile, PretixOrder, PretixOrderstatus
4+
from attendee.models import (
5+
PARTICIPATED_IN_PREVIOUS_EVENT_CHOICES,
6+
AttendeeProfile,
7+
PretixOrder,
8+
PretixOrderstatus,
9+
)
510
from portal.constants import (
611
CACHE_KEY_ATTENDEE_BREAKDOWN,
712
CACHE_KEY_ATTENDEE_COUNT,
13+
CACHE_KEY_ATTENDEE_FIRST_TIME_COUNT,
14+
CACHE_KEY_ATTENDEE_FIRST_TIME_PERCENT,
815
CACHE_KEY_DONATION_BREAKDOWN,
916
CACHE_KEY_DONATION_TOWARDS_GOAL_PERCENT,
1017
CACHE_KEY_DONATIONS_TOTAL_AMOUNT,
@@ -619,6 +626,11 @@ def get_attendee_stats_dict():
619626
stats_dict = {}
620627
stats_dict[CACHE_KEY_ATTENDEE_COUNT] = get_attendee_count_cache()
621628
stats_dict[CACHE_KEY_ATTENDEE_BREAKDOWN] = get_attendee_breakdown()
629+
stats_dict[CACHE_KEY_ATTENDEE_FIRST_TIME_COUNT] = get_first_time_attendee_count()
630+
stats_dict[CACHE_KEY_ATTENDEE_FIRST_TIME_PERCENT] = (
631+
get_first_time_attendee_percent()
632+
)
633+
622634
return stats_dict
623635

624636

@@ -657,6 +669,46 @@ def get_attendee_current_position_breakdown(attendee_profiles):
657669
return current_position_breakdown
658670

659671

672+
def get_first_time_attendee_count():
673+
"""Returns the count of first-time attendees."""
674+
first_time_attendee_count = cache.get(CACHE_KEY_ATTENDEE_FIRST_TIME_COUNT)
675+
if not first_time_attendee_count:
676+
first_time_attendee_count = AttendeeProfile.objects.filter(
677+
order__status=PretixOrderstatus.PAID,
678+
participated_in_previous_event__contains=[
679+
PARTICIPATED_IN_PREVIOUS_EVENT_CHOICES[2][0]
680+
],
681+
).count()
682+
683+
cache.set(
684+
CACHE_KEY_ATTENDEE_FIRST_TIME_COUNT,
685+
first_time_attendee_count,
686+
STATS_CACHE_TIMEOUT,
687+
)
688+
return first_time_attendee_count
689+
690+
691+
def get_first_time_attendee_percent():
692+
"""Returns the percent of first-time attendees."""
693+
cache.clear()
694+
first_time_attendee_percent = cache.get(CACHE_KEY_ATTENDEE_FIRST_TIME_PERCENT)
695+
if not first_time_attendee_percent:
696+
attendee_total_count = get_attendee_count_cache()
697+
attendee_first_time_count = get_first_time_attendee_count()
698+
first_time_attendee_percent = (
699+
(attendee_first_time_count / attendee_total_count) * 100
700+
if attendee_total_count > 0
701+
else 0
702+
)
703+
704+
cache.set(
705+
CACHE_KEY_ATTENDEE_FIRST_TIME_PERCENT,
706+
first_time_attendee_percent,
707+
STATS_CACHE_TIMEOUT,
708+
)
709+
return first_time_attendee_percent
710+
711+
660712
def get_attendee_breakdown():
661713
"""Returns the attendee demographic breakdown stats."""
662714
attendee_breakdown = cache.get(CACHE_KEY_ATTENDEE_BREAKDOWN)

portal/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
SPONSORSHIP_GOAL = "sponsorship_goal"
3434

3535
CACHE_KEY_ATTENDEE_COUNT = "attendee_count"
36+
CACHE_KEY_ATTENDEE_FIRST_TIME_COUNT = "attendee_first_time_count"
37+
CACHE_KEY_ATTENDEE_FIRST_TIME_PERCENT = "attendee_first_time_percent"
38+
3639
CACHE_KEY_ATTENDEE_BREAKDOWN = "attendee_breakdown"
3740
CACHE_KEY_ATTENDEE_BY_ROLE = "attendee_by_role"
3841
CACHE_KEY_ATTENDEE_BY_COUNTRY = "attendee_by_country"

templates/attendee/attendee_stats.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
<h3 id="attendee_demographic">
22
Attendee Demographic
33
</h3>
4+
<div class="container border rounded-3 p-3 mb-4">
5+
<div class="row">
6+
<div class="col-sm-4">
7+
<div class="card border-0">
8+
<div class="card-body">
9+
<h5 class="card-title">
10+
Registered Attendees
11+
</h5>
12+
<h1 class="display-4 text-info">
13+
{{ stats.attendee_count }}
14+
</h1>
15+
</div>
16+
</div>
17+
</div>
18+
<div class="col-sm-4">
19+
<div class="card border-0">
20+
<div class="card-body">
21+
<h5 class="card-title">
22+
First Time Attendees
23+
</h5>
24+
<h1 class="display-4 text-success">
25+
{{ stats.attendee_first_time_count }}
26+
</h1>
27+
</div>
28+
</div>
29+
</div>
30+
<div class="col-sm-4">
31+
<div class="card border-0 text-bg-success">
32+
<div class="card-body">
33+
<h5 class="card-title">
34+
% of First Time Attendees
35+
</h5>
36+
<h1 class="display-4 text-warning">
37+
{{ stats.attendee_first_time_percent |floatformat:0 }}%
38+
</h1>
39+
</div>
40+
</div>
41+
</div>
42+
</div>
43+
</div>
444
{% for breakdown in stats.attendee_breakdown %}
545
<div class="container border rounded-3 p-3 mb-4">
646
<div class="row">

0 commit comments

Comments
 (0)