Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions apps/volunteer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,32 @@
from flask import current_app as app
from flask_login import current_user

from apps.common import feature_enabled
from models.volunteer import Volunteer

volunteer = Blueprint("volunteer", __name__)


# This is like require_permission but redirects to volunteer sign-up
# To be enabled once I'm happy the recap page is working.
# @volunteer.before_app_request
# def check_site_state() -> ResponseReturnValue | None:
# if request.blueprint != "volunteer" or "recap" in request.path:
# return None

# if get_site_state() == "after-event" and not Volunteer.get_for_user(current_user).is_volunteer_admin:
# return redirect(url_for(".recap"))


# This is like require_permission but redirects to volunteer sign-up/info depending
# on feature flags.
def require_volunteer_permission(permission):
def call(f, *args, **kwargs):
if current_user.is_authenticated:
if not Volunteer.get_for_user(current_user):
return redirect(url_for("volunteer.sign_up", next=request.path))
if feature_enabled("VOLUNTEERS_SIGNUP"):
return redirect(url_for("volunteer.sign_up", next=request.path))

return redirect(url_for("base.page", page_name="volunteering"))
if current_user.has_permission(permission):
return f(*args, **kwargs)
abort(404)
Expand All @@ -115,6 +130,7 @@ def volunteer_variables():
buildup, # noqa: F401
choose_roles, # noqa: F401
main, # noqa: F401
recap, # noqa: F401
role_admin, # noqa: F401
schedule, # noqa: F401
sign_up, # noqa: F401
Expand Down
41 changes: 41 additions & 0 deletions apps/volunteer/recap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from functools import reduce

from flask import flash, redirect, render_template, url_for
from flask.typing import ResponseReturnValue
from flask_login import current_user

from models.volunteer.volunteer import Volunteer

from . import v_admin_required, v_user_required, volunteer


def _recap_for(volunteer: Volunteer) -> ResponseReturnValue:
total_shifts = reduce(
lambda x, y: x + y, [entry.voucher_contribution for entry in current_user.shift_entries]
)

return render_template(
"volunteer/recap.html",
volunteer=volunteer,
shift_entries=current_user.shift_entries,
registered_for_build=volunteer.registered_for_buildup,
total_shifts=total_shifts,
qualifies_for_voucher=(total_shifts >= 2),
)


@volunteer.route("/recap")
@v_user_required
def recap() -> ResponseReturnValue:
return _recap_for(Volunteer.get_for_user(current_user))


@volunteer.route("/recap/<email>")
@v_admin_required
def recap_by_email(email: str) -> ResponseReturnValue:
volunteer = Volunteer.get_by_email(email)
if volunteer is None:
flash(f"No volunteer was found with email address {email}. Showing your recap instead.")
return redirect(url_for(".recap"))

return _recap_for(volunteer)
7 changes: 7 additions & 0 deletions models/volunteer/shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ class ShiftEntry(BaseModel):
user: Mapped[User] = relationship(back_populates="shift_entries")
shift: Mapped[Shift] = relationship(back_populates="entries")

@property
def voucher_contribution(self) -> Decimal:
if self.state not in (ShiftEntryState.SIGNED_UP, ShiftEntryState.NO_SHOW, ShiftEntryState.ABANDONED):
return self.shift.multiplier

return Decimal(0)

def set_state(self, state: str | ShiftEntryState) -> None:
if isinstance(state, str):
try:
Expand Down
57 changes: 57 additions & 0 deletions templates/volunteer/recap.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{% extends 'base.html' %}
{% block title %}
EMF Volunteer Recap
{% endblock %}

{% block body %}
<h2>Volunteering Recap - {{ volunteer.nickname }}</h2>
{% if registered_for_build %}
<div class="alert alert-success">
<p>
You have qualified for an EMF 2028 pre-sale voucher by helping with build or teardown, many thanks!
{% if total_shifts > 0 %}
You also completed {{ total_shifts }} qualifying shifts.
{% endif %}
</p>
</div>
{% elif qualifies_for_voucher %}
<div class="alert alert-success">
<p>You have qualified for an EMF 2028 pre-sale ticket voucher with {{ total_shifts }} qualifying shifts.</p>
</div>
{% else %}
<div class="alert alert-danger">
<p>Sorry, you have not qualified for an EMF 2028 pre-sale ticket voucher with {{ total_shifts }} qualifying shifts.</p>
<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>
</div>
{% endif %}

<p>
Qualifying for a voucher required completion of 2 full volunteer shifts. Some shifts have multipliers applied
meaning that they might count as more or less than one shift. Shifts only qualify if you were recorded as having
arrived for the shift, and not having abandoned it before completion.
</p>
<table class="table table-striped responsive-table">
<thead>
<tr>
<th>Day</th>
<th>Time</th>
<th>Role</th>
<th>State</th>
<th>Multiplier</th>
<th>Contribution</th>
</tr>
</thead>
<tbody>
{% for entry in shift_entries %}
<tr>
<td>{{ entry.shift.local_start.strftime("%a %d") }}</td>
<td>{{ entry.shift.local_start.strftime("%H:%M") }} to {{ entry.shift.local_end.strftime("%H:%M") }}</td>
<td>{{ entry.shift.role.name }}</td>
<td>{{ entry.state | replace("_", " ") | capitalize }}</td>
<td>{{ entry.shift.multiplier.normalize() }}x</td>
<td>{{ entry.voucher_contribution }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
Loading