Skip to content

Commit

Permalink
Allow opt-out of free shirts
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsuta committed Oct 15, 2024
1 parent 62afc9e commit 5c2ba72
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Add ability to opt out of free shirts
Revision ID: 128e7228f182
Revises: 2e4feb6c2d18
Create Date: 2024-10-15 19:55:59.416212
"""


# revision identifiers, used by Alembic.
revision = '128e7228f182'
down_revision = '2e4feb6c2d18'
branch_labels = None
depends_on = None

from alembic import op
import sqlalchemy as sa



try:
is_sqlite = op.get_context().dialect.name == 'sqlite'
except Exception:
is_sqlite = False

if is_sqlite:
op.get_context().connection.execute('PRAGMA foreign_keys=ON;')
utcnow_server_default = "(datetime('now', 'utc'))"
else:
utcnow_server_default = "timezone('utc', current_timestamp)"

def sqlite_column_reflect_listener(inspector, table, column_info):
"""Adds parenthesis around SQLite datetime defaults for utcnow."""
if column_info['default'] == "datetime('now', 'utc')":
column_info['default'] = utcnow_server_default

sqlite_reflect_kwargs = {
'listeners': [('column_reflect', sqlite_column_reflect_listener)]
}

# ===========================================================================
# HOWTO: Handle alter statements in SQLite
#
# def upgrade():
# if is_sqlite:
# with op.batch_alter_table('table_name', reflect_kwargs=sqlite_reflect_kwargs) as batch_op:
# batch_op.alter_column('column_name', type_=sa.Unicode(), server_default='', nullable=False)
# else:
# op.alter_column('table_name', 'column_name', type_=sa.Unicode(), server_default='', nullable=False)
#
# ===========================================================================


def upgrade():
op.add_column('attendee', sa.Column('shirt_opt_out', sa.Integer(), server_default='227291107', nullable=False))


def downgrade():
op.drop_column('attendee', 'shirt_opt_out')
20 changes: 6 additions & 14 deletions uber/configspec.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,12 @@ rated_great = string(default="Staffer went above and beyond")
default_wristband = string(default="red")
__many__ = string

[[shirt_opt_out]]
opt_in = string(default="Opt out of receiving a shirt?")
staff_opt_out = string(default="I would like to opt out of receiving my staff shirt.")
event_opt_out = string(default="I would like to opt out of receiving my free shirt for volunteering.")
all_opt_out = string(default="I would like to opt out of ANY free shirts.")

[[tracking]]
created = string(default="created")
updated = string(default="updated")
Expand All @@ -1582,20 +1588,6 @@ full = string(default="All Info")
[[food_restriction]]
vegan = string(default="Vegan")

[[hotel_room_type]]
room_double = string(default="Double Room")
room_king = string(default="King Room")

[[suite_room_type]]
suite_big = string(default="Big Suite")
suite_medium = string(default="Medium Suite")
suite_tiny = string(default="Tiny Suite")

[[hotel_priorities]]
hotel_room_type = string(default="Hotel Room Type")
hotel_selection = string(default="Hotel Selection")
hotel_dates = string(default="Check-In and Check-Out Dates")

[[sandwich]]

[[dealer_status]]
Expand Down
40 changes: 36 additions & 4 deletions uber/models/attendee.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ class Attendee(MagModel, TakesPaymentMixin):
shirt = Column(Choice(c.SHIRT_OPTS), default=c.NO_SHIRT)
staff_shirt = Column(Choice(c.STAFF_SHIRT_OPTS), default=c.NO_SHIRT)
num_event_shirts = Column(Choice(c.STAFF_EVENT_SHIRT_OPTS, allow_unspecified=True), default=-1)
shirt_opt_out = Column(Choice(c.SHIRT_OPT_OUT_OPTS), default=c.OPT_IN)
can_spam = Column(Boolean, default=False)
regdesk_info = Column(UnicodeText, admin_only=True)
extra_merch = Column(UnicodeText, admin_only=True)
Expand Down Expand Up @@ -1225,7 +1226,8 @@ def has_extras(self):
@property
def shirt_size_marked(self):
if c.STAFF_SHIRT_OPTS == c.SHIRT_OPTS:
return self.shirt not in [c.NO_SHIRT, c.SIZE_UNKNOWN]
return self.shirt not in [c.NO_SHIRT, c.SIZE_UNKNOWN] or (
not self.gets_staff_shirt and not self.num_event_shirts_owed)
else:
return (not self.num_event_shirts_owed or self.shirt not in [c.NO_SHIRT, c.SIZE_UNKNOWN]) and (
not self.gets_staff_shirt or self.staff_shirt not in [c.NO_SHIRT, c.SIZE_UNKNOWN])
Expand All @@ -1236,6 +1238,7 @@ def shirt_info_marked(self):
return (self.num_event_shirts != -1 or not c.STAFF_EVENT_SHIRT_OPTS) and self.shirt_size_marked
elif self.volunteer_event_shirt_eligible:
return self.shirt_size_marked
return self.shirt_opt_out != c.OPT_IN

@property
def is_group_leader(self):
Expand Down Expand Up @@ -1401,9 +1404,9 @@ def gets_swadge(self):
@property
def paid_for_a_shirt(self):
return self.amount_extra >= c.SHIRT_LEVEL

@property
def num_free_event_shirts(self):
def num_potential_free_event_shirts(self):
"""
If someone is staff-shirt-eligible, we use the number of event shirts they have selected (if any).
Volunteers also get a free event shirt. Staff get an event shirt if staff shirts are turned off for the event.
Expand All @@ -1412,6 +1415,12 @@ def num_free_event_shirts(self):
return max(0, self.num_event_shirts) if self.gets_staff_shirt else bool(
self.volunteer_event_shirt_eligible or (self.badge_type == c.STAFF_BADGE and c.HOURS_FOR_SHIRT))

@property
def num_free_event_shirts(self):
if self.shirt_opt_out in [c.EVENT_OPT_OUT, c.ALL_OPT_OUT]:
return 0
return self.num_potential_free_event_shirts

@property
def volunteer_event_shirt_eligible(self):
return bool(c.VOLUNTEER_RIBBON in self.ribbon_ints and c.HOURS_FOR_SHIRT)
Expand All @@ -1426,10 +1435,16 @@ def num_event_shirts_owed(self):
int(self.paid_for_a_shirt),
self.num_free_event_shirts
])

@property
def could_get_staff_shirt(self):
return bool(self.badge_type == c.STAFF_BADGE and c.SHIRTS_PER_STAFFER > 0)

@property
def gets_staff_shirt(self):
return bool(self.badge_type == c.STAFF_BADGE and c.SHIRTS_PER_STAFFER > 0)
if self.shirt_opt_out in [c.STAFF_OPT_OUT, c.ALL_OPT_OUT]:
return False
return self.could_get_staff_shirt

@property
def num_staff_shirts_owed(self):
Expand All @@ -1438,6 +1453,23 @@ def num_staff_shirts_owed(self):
@property
def gets_any_kind_of_shirt(self):
return self.gets_staff_shirt or self.num_event_shirts_owed > 0

@property
def shirt_opt_out_opts(self):
opt_list = []
for key, val in c.SHIRT_OPT_OUT_OPTS:
if key == c.STAFF_OPT_OUT and not self.could_get_staff_shirt:
continue
elif key == c.EVENT_OPT_OUT and self.num_potential_free_event_shirts == 0:
continue
elif key == c.ALL_OPT_OUT and (not self.could_get_staff_shirt or not c.STAFF_EVENT_SHIRT_OPTS):
continue
else:
if self.shirt_opt_out != c.OPT_IN and key == c.OPT_IN:
# Changes the text to make the form a little clearer
val = f"I would like to receive my {'staff' if self.could_get_staff_shirt else 'free'} shirt!"
opt_list.append((key, val))
return opt_list

@property
def has_personalized_badge(self):
Expand Down
32 changes: 18 additions & 14 deletions uber/site_sections/staffing.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import cherrypy
from datetime import datetime, timedelta
from pockets.autolog import log
import ics

from uber.config import c
from uber.custom_tags import safe_string
from uber.decorators import ajax, ajax_gettable, all_renderable, check_shutdown, csrf_protected, render, public
from uber.errors import HTTPRedirect
from uber.models import Attendee
from uber.utils import check_csrf, create_valid_user_supplied_redirect_url, ensure_csrf_token_exists, localized_now


Expand Down Expand Up @@ -42,21 +44,23 @@ def food_restrictions(self, session, message='', **params):
}

@check_shutdown
def shirt_size(self, session, message='', shirt=None, staff_shirt=None, num_event_shirts=None, csrf_token=None):
def shirt_size(self, session, message='', **params):
attendee = session.logged_in_volunteer()
if shirt is not None or staff_shirt is not None:
check_csrf(csrf_token)
if (shirt and not int(shirt)) or (attendee.gets_staff_shirt and
c.STAFF_SHIRT_OPTS != c.SHIRT_OPTS and not int(staff_shirt)):
message = 'You must select a shirt size'
else:
if shirt:
attendee.shirt = int(shirt)
if staff_shirt:
attendee.staff_shirt = int(staff_shirt)
if c.STAFF_EVENT_SHIRT_OPTS and c.BEFORE_VOLUNTEER_SHIRT_DEADLINE and num_event_shirts:
attendee.num_event_shirts = int(num_event_shirts)
raise HTTPRedirect('index?message={}', 'Shirt info uploaded')
if cherrypy.request.method == "POST":
check_csrf(params.get('csrf_token'))
test_attendee = Attendee(**attendee.to_dict())
test_attendee.apply(params)

if c.STAFF_EVENT_SHIRT_OPTS and test_attendee.gets_staff_shirt and test_attendee.num_event_shirts == -1:
message = "Please indicate your preference for shirt type."
elif not test_attendee.shirt_size_marked:
message = "Please select a shirt size."

if not message:
for attr in ['shirt', 'staff_shirt', 'num_event_shirts', 'shirt_opt_out']:
if params.get(attr):
setattr(attendee, attr, int(params.get(attr)))
raise HTTPRedirect('index?message={}', 'Shirt info uploaded.')

return {
'message': message,
Expand Down
4 changes: 2 additions & 2 deletions uber/templates/staffing/shirt_item.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% import 'macros.html' as macros %}
{% if (c.HOURS_FOR_SHIRT and attendee.gets_any_kind_of_shirt or attendee.gets_staff_shirt) and c.PRE_CON %}
{% if c.PRE_CON and (c.HOURS_FOR_SHIRT and attendee.num_potential_free_event_shirts) or attendee.could_get_staff_shirt %}
<li>
{{ macros.checklist_image(attendee.shirt_info_marked) }}
{% if not attendee.placeholder %}
Expand All @@ -8,7 +8,7 @@
Let us know
{% endif %}
your shirt size
{% if c.STAFF_EVENT_SHIRT_OPTS and attendee.gets_staff_shirt %}
{% if c.STAFF_EVENT_SHIRT_OPTS and attendee.could_get_staff_shirt %}
and your preference of staff or event shirt
{% endif %}
{% if c.VOLUNTEER_SHIRT_DEADLINE %}
Expand Down
45 changes: 34 additions & 11 deletions uber/templates/staffing/shirt_size.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ <h2> Tell Us Your Shirt Size </h2>

<p>One of the perks of volunteering is a t-shirt for anyone {% if c.HOURS_FOR_SHIRT and attendee.volunteer_event_shirt_eligible %}who takes at least {{ c.HOURS_FOR_SHIRT }} weighted hours worth of shifts{% else %} on staff{% endif %}.

{% if attendee.gets_staff_shirt %}
{% if attendee.could_get_staff_shirt %}
<h3>Staff Shirt Policy</h3>
<p>
Event staff are given a themed staff t-shirt that serves as a uniform for the event, and is theirs to keep forever.
Expand Down Expand Up @@ -50,23 +50,31 @@ <h3>Staff Shirt Policy</h3>
<form method="post" action="shirt_size" id="shirt_size" class="form-inline">
<div class="row g-sm-3 row-cols-auto">
{{ csrf_token() }}
{% if c.STAFF_EVENT_SHIRT_OPTS or attendee.num_event_shirts_owed %}
<div class="col mb-3">
<label class="form-text">
Shirt Opt In/Out
</label>
<select class="form-select" name="shirt_opt_out" id="shirt_opt_out_select">
{{ options(attendee.shirt_opt_out_opts, attendee.shirt_opt_out) }}
</select>
</div>
{% if c.STAFF_EVENT_SHIRT_OPTS or attendee.num_potential_free_event_shirts %}
<div class="col mb-3" id="event_shirt">
<label class="form-text">
{% if attendee.gets_staff_shirt and c.STAFF_SHIRT_OPTS != c.SHIRT_OPTS %}Event {% endif %}Shirt Size
{% if attendee.could_get_staff_shirt and c.STAFF_SHIRT_OPTS != c.SHIRT_OPTS %}Event {% endif %}Shirt Size
</label>
<select class="form-select" name="shirt">
<select class="form-select" name="shirt" id="shirt_select">
<option value="{{ c.NO_SHIRT }}">Select a size</option>
{{ options(c.SHIRT_OPTS[1:], attendee.shirt) }}
</select>
</div>
{% endif %}
{% if attendee.gets_staff_shirt and c.STAFF_SHIRT_OPTS != c.SHIRT_OPTS %}
{% if attendee.could_get_staff_shirt and c.STAFF_SHIRT_OPTS != c.SHIRT_OPTS %}
<div class="col mb-3" id="staff_shirt">
<label class="form-text">
Staff Shirt Size
</label>
<select class="form-select" name="staff_shirt">
<select class="form-select" name="staff_shirt" id="staff_shirt_select">
<option value="{{ c.NO_SHIRT }}">Select a size</option>
{{ options(c.STAFF_SHIRT_OPTS[1:], attendee.staff_shirt) }}
</select>
Expand All @@ -78,15 +86,30 @@ <h3>Staff Shirt Policy</h3>
</div>
</div>
<script type="text/javascript">
if($.field('num_event_shirts')) {
showOrHideSizeOpts = function() {
var showOrHideSizeOpts = function() {
if($('#staff_shirt').length) {
$('#staff_shirt').toggle($('input[name="num_event_shirts"]:checked').val() < {{ c.SHIRTS_PER_STAFFER }});
}
$('#event_shirt').toggle($('input[name="num_event_shirts"]:checked').val() > 0 || {{ (c.STAFF_SHIRT_OPTS == c.SHIRT_OPTS)|lower }});
}
showOrHideSizeOpts();
$('input[name="num_event_shirts"]').bind('change',showOrHideSizeOpts);
}

var disableSizeOpts = function() {
let opt_out = $('#shirt_opt_out_select').val();
if($('#staff_shirt').length) {
$('#staff_shirt_select').prop('disabled', ['{{ c.STAFF_OPT_OUT }}', '{{ c.ALL_OPT_OUT }}'].includes(opt_out));
$('#shirt_select').prop('disabled', ['{{ c.EVENT_OPT_OUT }}', '{{ c.ALL_OPT_OUT }}'].includes(opt_out));
} else {
$('#shirt_select').prop('disabled', opt_out != '{{ c.OPT_IN }}');
}
}

$().ready(function () {
disableSizeOpts();
$('#shirt_opt_out_select').on('change', disableSizeOpts);
if($.field('num_event_shirts')) {
showOrHideSizeOpts();
$('input[name="num_event_shirts"]').bind('change',showOrHideSizeOpts);
}
})
</script>
{% endblock %}

0 comments on commit 5c2ba72

Please sign in to comment.