Skip to content

Commit a084d07

Browse files
committed
Volunteer recap page
This page allows people who volunteered to see what shifts they did and whether they qualified for a voucher, which will hopefully mean that people can catch inaccurate data before we start issuing 2028 vouchers and everyone has forgotten what happened.
1 parent a4eaf5d commit a084d07

4 files changed

Lines changed: 123 additions & 2 deletions

File tree

apps/volunteer/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,32 @@
7878
from flask import current_app as app
7979
from flask_login import current_user
8080

81+
from apps.common import feature_enabled
8182
from models.volunteer import Volunteer
8283

8384
volunteer = Blueprint("volunteer", __name__)
8485

8586

86-
# This is like require_permission but redirects to volunteer sign-up
87+
# To be enabled once I'm happy the recap page is working.
88+
# @volunteer.before_app_request
89+
# def check_site_state() -> ResponseReturnValue | None:
90+
# if request.blueprint != "volunteer" or "recap" in request.path:
91+
# return None
92+
93+
# if get_site_state() == "after-event" and not Volunteer.get_for_user(current_user).is_volunteer_admin:
94+
# return redirect(url_for(".recap"))
95+
96+
97+
# This is like require_permission but redirects to volunteer sign-up/info depending
98+
# on feature flags.
8799
def require_volunteer_permission(permission):
88100
def call(f, *args, **kwargs):
89101
if current_user.is_authenticated:
90102
if not Volunteer.get_for_user(current_user):
91-
return redirect(url_for("volunteer.sign_up", next=request.path))
103+
if feature_enabled("VOLUNTEERS_SIGNUP"):
104+
return redirect(url_for("volunteer.sign_up", next=request.path))
105+
106+
return redirect(url_for("base.page", page_name="volunteering"))
92107
if current_user.has_permission(permission):
93108
return f(*args, **kwargs)
94109
abort(404)
@@ -115,6 +130,7 @@ def volunteer_variables():
115130
buildup, # noqa: F401
116131
choose_roles, # noqa: F401
117132
main, # noqa: F401
133+
recap, # noqa: F401
118134
role_admin, # noqa: F401
119135
schedule, # noqa: F401
120136
sign_up, # noqa: F401

apps/volunteer/recap.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from functools import reduce
2+
3+
from flask import flash, redirect, render_template, url_for
4+
from flask.typing import ResponseReturnValue
5+
from flask_login import current_user
6+
7+
from models.volunteer.volunteer import Volunteer
8+
9+
from . import v_admin_required, v_user_required, volunteer
10+
11+
12+
def _recap_for(volunteer: Volunteer) -> ResponseReturnValue:
13+
total_shifts = reduce(
14+
lambda x, y: x + y, [entry.voucher_contribution for entry in current_user.shift_entries]
15+
)
16+
17+
return render_template(
18+
"volunteer/recap.html",
19+
volunteer=volunteer,
20+
shift_entries=current_user.shift_entries,
21+
registered_for_build=volunteer.registered_for_buildup,
22+
total_shifts=total_shifts,
23+
qualifies_for_voucher=(total_shifts >= 2),
24+
)
25+
26+
27+
@volunteer.route("/recap")
28+
@v_user_required
29+
def recap() -> ResponseReturnValue:
30+
return _recap_for(Volunteer.get_for_user(current_user))
31+
32+
33+
@volunteer.route("/recap/<email>")
34+
@v_admin_required
35+
def recap_by_email(email: str) -> ResponseReturnValue:
36+
volunteer = Volunteer.get_by_email(email)
37+
if volunteer is None:
38+
flash(f"No volunteer was found with email address {email}. Showing your recap instead.")
39+
return redirect(url_for(".recap"))
40+
41+
return _recap_for(volunteer)

models/volunteer/shift.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ class ShiftEntry(BaseModel):
8484
user: Mapped[User] = relationship(back_populates="shift_entries")
8585
shift: Mapped[Shift] = relationship(back_populates="entries")
8686

87+
@property
88+
def voucher_contribution(self) -> Decimal:
89+
if self.state not in (ShiftEntryState.SIGNED_UP, ShiftEntryState.NO_SHOW, ShiftEntryState.ABANDONED):
90+
return self.shift.multiplier
91+
92+
return 0
93+
8794
def set_state(self, state: str | ShiftEntryState) -> None:
8895
if isinstance(state, str):
8996
try:

templates/volunteer/recap.html

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{% extends 'base.html' %}
2+
{% block title %}
3+
EMF Volunteer Recap
4+
{% endblock %}
5+
6+
{% block body %}
7+
<h2>Volunteering Recap - {{ volunteer.nickname }}</h2>
8+
{% if registered_for_build %}
9+
<div class="alert alert-success">
10+
<p>
11+
You have qualified for an EMF 2028 pre-sale voucher by helping with build or teardown, many thanks!
12+
{% if total_shifts > 0 %}
13+
You also completed {{ total_shifts }} qualifying shifts.
14+
{% endif %}
15+
</p>
16+
</div>
17+
{% elif qualifies_for_voucher %}
18+
<div class="alert alert-success">
19+
<p>You have qualified for an EMF 2028 pre-sale ticket voucher with {{ total_shifts }} qualifying shifts.</p>
20+
</div>
21+
{% else %}
22+
<div class="alert alert-danger">
23+
<p>Sorry, you have not qualified for an EMF 2028 pre-sale ticket voucher with {{ total_shifts }} qualifying shifts.</p>
24+
<p>If you think this is in error please contact us at <a href="mailto:volunteer@emfcamp.org">volunteer@emfcamp.org</a> letting us know why and we'll do our best to resolve it.</p>
25+
</div>
26+
{% endif %}
27+
28+
<p>
29+
Qualifying for a voucher required completion of 2 full volunteer shifts. Some shifts have multipliers applied
30+
meaning that they might count as more or less than one shift. Shifts only qualify if you were recorded as having
31+
arrived for the shift, and not having abandoned it before completion.
32+
</p>
33+
<table class="table table-striped responsive-table">
34+
<thead>
35+
<tr>
36+
<th>Day</th>
37+
<th>Time</th>
38+
<th>Role</th>
39+
<th>State</th>
40+
<th>Multiplier</th>
41+
<th>Contribution</th>
42+
</tr>
43+
</thead>
44+
<tbody>
45+
{% for entry in shift_entries %}
46+
<tr>
47+
<td>{{ entry.shift.local_start.strftime("%a %d") }}</td>
48+
<td>{{ entry.shift.local_start.strftime("%H:%M") }} to {{ entry.shift.local_end.strftime("%H:%M") }}</td>
49+
<td>{{ entry.shift.role.name }}</td>
50+
<td>{{ entry.state | replace("_", " ") | capitalize }}</td>
51+
<td>{{ entry.shift.multiplier.normalize() }}x</td>
52+
<td>{{ entry.voucher_contribution }}</td>
53+
</tr>
54+
{% endfor %}
55+
</tbody>
56+
</table>
57+
{% endblock %}

0 commit comments

Comments
 (0)