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
539 changes: 2 additions & 537 deletions apps/base/dev/tasks.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions apps/volunteer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ def volunteer_variables():
bar_training, # noqa: F401
choose_roles, # noqa: F401
main, # noqa: F401
role_admin, # noqa: F401
schedule, # noqa: F401
sign_up, # noqa: F401
stats, # noqa: F401
team_admin, # noqa: F401
training, # noqa: F401
)
3 changes: 2 additions & 1 deletion apps/volunteer/admin/role.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from main import db
from models.volunteer.role import Role, RoleAdmin
from models.volunteer.role import Role, RoleAdmin, Team

from ..flask_admin_base import VolunteerModelView
from . import volunteer_admin

volunteer_admin.add_view(VolunteerModelView(Team, db.session, name="Teams"))
volunteer_admin.add_view(VolunteerModelView(Role, db.session, name="Roles"))
volunteer_admin.add_view(VolunteerModelView(RoleAdmin, db.session, name="RoleAdmins"))
152 changes: 0 additions & 152 deletions apps/volunteer/choose_roles.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
from datetime import datetime, timedelta

from decorator import decorator
from flask import (
abort,
flash,
redirect,
render_template,
request,
url_for,
)
from flask import (
current_app as app,
)
from flask.typing import ResponseReturnValue
from flask_login import current_user
from sqlalchemy import select
from wtforms import BooleanField, FieldList, FormField, SubmitField
from wtforms.validators import InputRequired

from main import db, get_or_404
from models.volunteer.role import Role
from models.volunteer.shift import (
Shift,
ShiftEntry,
ShiftEntryState,
ShiftEntryStateException,
)
from models.volunteer.volunteer import Volunteer as VolunteerUser

from ..common import feature_enabled, feature_flag
Expand Down Expand Up @@ -114,26 +99,6 @@ def choose_role():
return render_template("volunteer/choose_role.html", form=form)


@volunteer.route("/role-admin", methods=["GET"])
@v_user_required
def role_admin_index():
roles = []
if (
current_user.has_permission("admin")
or current_user.has_permission("volunteer:manager")
or current_user.has_permission("volunteer:admin")
):
roles = Role.query.order_by("name").all()
else:
roles = [admin.role for admin in current_user.volunteer_admin_roles]

if len(roles) == 0:
flash("You're not an admin for any roles.")
redirect(url_for(".choose_role"))

return render_template("volunteer/role_admin_index.html", roles=roles)


@volunteer.route("/role/<int:role_id>", methods=["GET", "POST"])
@feature_flag("VOLUNTEERS_SIGNUP")
@v_user_required
Expand Down Expand Up @@ -162,120 +127,3 @@ def role(role_id):
role=role,
current_volunteer=current_volunteer,
)


@decorator
def role_admin_required(f, *args, **kwargs):
"""Check that current user has permissions to be RoleAdmin for role.id that is first entry in args"""
if current_user.is_authenticated:
if int(args[0]) in [ra.role_id for ra in current_user.volunteer_admin_roles] or (
current_user.has_permission("volunteer:admin") or current_user.has_permission("volunteer:manager")
):
return f(*args, **kwargs)
abort(404)
return app.login_manager.unauthorized()


@volunteer.route("role/<int:role_id>/admin")
@role_admin_required
def role_admin(role_id):
# Allow mocking the time for testing.
if "now" in request.args:
now = datetime.strptime(request.args["now"], "%Y-%m-%dT%H:%M")
else:
now = datetime.now()

limit = int(request.args.get("limit", "5"))
offset = int(request.args.get("offset", "0"))
role = get_or_404(db, Role, role_id)
cutoff = now - timedelta(minutes=30)
shifts = (
Shift.query.filter_by(role=role)
.filter(Shift.end >= cutoff)
.order_by(Shift.end)
.offset(offset)
.limit(limit)
.all()
)

active_shift_entries = (
ShiftEntry.query.filter(ShiftEntry.state == ShiftEntryState.ARRIVED)
.join(ShiftEntry.shift)
.filter(Shift.role_id == role.id)
.all()
)
pending_shift_entries = (
ShiftEntry.query.join(ShiftEntry.shift)
.filter(
Shift.start <= now - timedelta(minutes=15),
Shift.role == role,
ShiftEntry.state == ShiftEntryState.SIGNED_UP,
)
.all()
)

return render_template(
"volunteer/role_admin.html",
role=role,
shifts=shifts,
active_shift_entries=active_shift_entries,
pending_shift_entries=pending_shift_entries,
now=now,
offset=offset,
limit=limit,
)


@volunteer.route("role/<int:role_id>/set-state/<int:shift_id>/<int:user_id>", methods=["POST"])
@role_admin_required
def set_state(role_id: int, shift_id: int, user_id: int) -> ResponseReturnValue:
state = request.form["state"]

se = (
db.session.execute(
select(ShiftEntry).where(ShiftEntry.shift_id == shift_id, ShiftEntry.user_id == user_id)
)
.scalars()
.first()
)
if se is None:
abort(404)
if se.state != state:
try:
se.set_state(state)
except ShiftEntryStateException:
flash(f"{state} is not a valid state for this shift.")
else:
db.session.commit()

return redirect(url_for(".role_admin", role_id=role_id))


@volunteer.route("role/<int:role_id>/<int:shift_id>", methods=["POST"])
@role_admin_required
def update_shift(role_id: int, shift_id: int) -> ResponseReturnValue:
shift = get_or_404(db, Shift, shift_id)
shift.min_needed = int(request.form["min_needed"])
shift.max_needed = int(request.form["max_needed"])
db.session.add(shift)
db.session.commit()

flash("Shift requirements updated.")
return redirect(url_for(".role_admin", role_id=role_id, _anchor=f"shift-{shift.id}"))


@volunteer.route("role/<int:role_id>/volunteers")
@role_admin_required
def role_volunteers(role_id):
role = get_or_404(db, Role, role_id)
interested = VolunteerUser.query.join(VolunteerUser.interested_roles).filter(Role.id == role_id).all()
entries = ShiftEntry.query.join(ShiftEntry.shift).filter(Shift.role_id == role_id).all()
signed_up = list(set([se.user.volunteer for se in entries]))
completed = list(set([se.user.volunteer for se in entries if se.state == "completed"]))
return render_template(
"volunteer/role_volunteers.html",
role=role,
interested=interested,
signed_up=signed_up,
completed=completed,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ name: Badge Helper
description: Fix, replace and troubleshoot badges and their software.
full_description_md: |
Fix, replace and troubleshoot badges and their software.

Some experience of soldering and/or embedded software would like be useful, but if you're willing to learn we can teach you the basics so you can help people out.
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/arcade.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Arcade
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/badge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Badge
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/bands.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Bands
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/bar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Bar
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/content.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Content
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/info_desk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Info Desk
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/kitchen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Kitchen
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/null_sector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Null Sector
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/operations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Operations
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/phones.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Phones
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/shop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Shop
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/talks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Talks
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/video.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Video
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/volunteering.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Volunteering
1 change: 1 addition & 0 deletions apps/volunteer/data/teams/youth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: Youth
67 changes: 61 additions & 6 deletions apps/volunteer/init_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,80 @@
from typing import Any

from flask import current_app as app
from pendulum import parse
from yaml import safe_load

from apps.volunteer.shift_list import get_shift_list
from main import db
from models.volunteer.role import Role, Team
from models.volunteer.shift import Shift
from models.volunteer.venue import VolunteerVenue


def shifts():
for t in load_from_yaml("apps/volunteer/data/teams/*.yml"):
team = Team.from_dict(t)
app.logger.info(f"Adding team {team}")
db.session.add(team)

for r in load_from_yaml(f"apps/volunteer/data/roles/{t['slug']}/*.yml"):
role = Role.from_dict(r)
role.team = team
app.logger.info(f"Adding role {role}")
db.session.add(role)

for v in load_initial_venues():
venue = VolunteerVenue.get_by_name(v["name"]) or VolunteerVenue(name=v["name"])
venue.mapref = v["mapref"]
db.session.add(venue)

shift_list = get_shift_list()

for shift_role in shift_list:
role = Role.get_by_name(shift_role)
if role is None:
app.logger.error(f"Unknown role: {shift_role}")
continue

if role.shifts:
app.logger.info(f"Skipping making shifts for role: {role.name}")
continue

for shift_venue in shift_list[shift_role]:
venue = VolunteerVenue.get_by_name(shift_venue)
if venue is None:
app.logger.error(f"Unknown venue: {shift_venue}")
continue

for shift_range in shift_list[shift_role][shift_venue]:
shifts = Shift.generate_for(
role=role,
venue=venue,
first=parse(shift_range["first"]),
final=parse(shift_range["final"]),
min=shift_range["min"],
max=shift_range["max"],
base_duration=shift_range.get("base_duration", 120),
changeover=shift_range.get("changeover", 15),
)
for s in shifts:
db.session.add(s)

db.session.commit()


def load_from_yaml(path_glob: str) -> list[dict[str, Any]]:
"""Loads all YAML files from the passed path glob."""
items = []

for path in Path(app.root_path).glob(path_glob):
with open(path) as file:
items.append(safe_load(file))
item: dict[str, Any] = safe_load(file) | {"slug": path.stem}
items.append(item)

return items


def load_initial_venues() -> list[dict[str, Any]]:
"""Loads venue data."""
return load_from_yaml("apps/volunteer/data/venues/*.yml")


def load_initial_roles() -> list[dict[str, Any]]:
"""Loads role data."""
return load_from_yaml("apps/volunteer/data/roles/*.yml")
Loading
Loading