Skip to content

Commit

Permalink
Merge pull request #4242 from magfest/prereg-launch-fixes
Browse files Browse the repository at this point in the history
Fix several form and validation bugs for prereg launch
  • Loading branch information
kitsuta authored Sep 14, 2023
2 parents 2e00a9a + 08c8202 commit 721e089
Show file tree
Hide file tree
Showing 37 changed files with 175 additions and 163 deletions.
10 changes: 6 additions & 4 deletions alembic/versions/29b1b9a4e601_moves_departments_into_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@ def all_dept_ids_from_existing_locations(locations):
dept_ids.append(department_id)
return dept_ids


job_location_to_department_id = {i: _dept_id_from_location(i) for i in c.JOB_LOCATIONS.keys()}
job_interests_to_department_id = {i: job_location_to_department_id[i] for i in c.JOB_INTERESTS.keys() if i in job_location_to_department_id}
job_locations = getattr(c, 'JOB_LOCATIONS', {})
job_interests = getattr(c, 'JOB_INTERESTS', {})
job_location_to_department_id = {i: _dept_id_from_location(i) for i in job_locations.keys()}
job_interests_to_department_id = {i: job_location_to_department_id[i] for i in job_interests.keys() if i in job_location_to_department_id}


job_table = table(
Expand Down Expand Up @@ -192,7 +193,8 @@ def all_dept_ids_from_existing_locations(locations):

def _upgrade_job_departments():
connection = op.get_bind()
for value, name in c.JOB_LOCATIONS.items():
job_locations = getattr(c, 'JOB_LOCATIONS', {})
for value, name in job_locations.items():
department_id = job_location_to_department_id[value]
op.execute(
department_table.insert().values({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Remove panels_interest column from attendees
Revision ID: 41c34c63d54c
Revises: 9122d9b6f62c
Create Date: 2023-09-13 22:08:44.699427
"""


# revision identifiers, used by Alembic.
revision = '41c34c63d54c'
down_revision = '9122d9b6f62c'
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():
with op.batch_alter_table("attendee") as batch_op:
batch_op.drop_column('panel_interest')


def downgrade():
op.add_column('attendee', sa.Column('panel_interest', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False))
9 changes: 0 additions & 9 deletions test-defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,6 @@ con_ops = "Fest Ops"
console = "Consoles"
arcade = "Arcade"

[[job_location]]
# ensure that "arcade" and "consoles" exist in any model columns using "job_location"
console = "Console"
arcade = "Arcade"
con_ops = "Fest Ops"
food_prep = "Staff Suite"
regdesk = "Registration"
stops = "Staffing Ops"

[[badge]]
attendee_badge = "Attendee"
child_badge = "Minor"
Expand Down
15 changes: 6 additions & 9 deletions tests/data/import_test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ def all_dept_ids_from_existing_locations(locations):
dept_ids.append(department_id)
return dept_ids


job_location_to_department_id = {i: _dept_id_from_location(i) for i in c.JOB_LOCATIONS.keys()}
job_locations = getattr(c, 'JOB_LOCATIONS', {})
job_interests = getattr(c, 'JOB_INTERESTS', {})
job_location_to_department_id = {i: _dept_id_from_location(i) for i in job_locations.keys()}
job_interests_to_department_id = {
i: job_location_to_department_id[i] for i in c.JOB_INTERESTS.keys() if i in job_location_to_department_id}
i: job_location_to_department_id[i] for i in job_interests.keys() if i in job_location_to_department_id}


TEST_DATA_FILE = join(os.path.dirname(__file__), 'test_data.json')
Expand Down Expand Up @@ -134,7 +135,8 @@ def import_events(session):


def import_jobs(session):
job_locs, _ = zip(*c.JOB_LOCATION_OPTS)
job_location_opts = getattr(c, 'JOB_LOCATION_OPTS', [])
job_locs, _ = zip(*job_location_opts)
depts_known = []
for j in dump['jobs']:
if j['location'] in job_locs:
Expand All @@ -156,11 +158,6 @@ def import_jobs(session):

@entry_point
def import_uber_test_data(test_data_file):
if not c.JOB_LOCATION_OPTS:
print("JOB_LOCATION_OPTS is empty! "
"Try copying the [[job_location]] section from test-defaults.ini to your development.ini.")
exit(1)

with open(test_data_file) as f:
global dump
dump = json.load(f)
Expand Down
11 changes: 0 additions & 11 deletions tests/uber/models/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,6 @@ def test_at_door_badge_opts_with_extra(self, monkeypatch):
assert dict(c.AT_THE_DOOR_BADGE_OPTS).keys() == {c.ATTENDEE_BADGE, c.ONE_DAY_BADGE, c.CONTRACTOR_BADGE}


class TestStaffGetFood:
def test_job_locations_with_food_prep(self):
assert c.STAFF_GET_FOOD

def test_job_locations_without_food_prep(self, monkeypatch):
job_locations = dict(c.JOB_LOCATIONS)
del job_locations[c.FOOD_PREP]
monkeypatch.setattr(c, 'JOB_LOCATIONS', job_locations)
assert not c.STAFF_GET_FOOD


class TestDealerConfig:
def test_dealer_reg_open(self, monkeypatch):
monkeypatch.setattr(c, 'DEALER_REG_START', localized_now() - timedelta(days=1))
Expand Down
1 change: 0 additions & 1 deletion uber/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,6 @@ class AttendeeLookup:
'is_dept_head': True,
'ribbon_labels': True,
'public_id': True,
'covid_ready': True,
}

fields_full = dict(fields, **{
Expand Down
4 changes: 1 addition & 3 deletions uber/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ def FORMATTED_REG_TYPES(self):
if c.CHILD_BADGE in c.PREREG_BADGE_TYPES:
reg_type_opts.append({
'name': "12 and Under",
'desc': Markup("Attendees 12 and younger must be accompanied by an adult with a valid Attendee badge. \
'desc': Markup(f"Attendees 12 and younger at the start of {c.EVENT_NAME} must be accompanied by an adult with a valid Attendee badge. \
<br/><br/><span class='form-text text-danger'>Price is always half that of the Single Attendee badge price.</span>"),
'value': c.CHILD_BADGE,
'price': str(c.BADGE_PRICE - math.ceil(c.BADGE_PRICE / 2)),
Expand Down Expand Up @@ -1280,8 +1280,6 @@ def _unrepr(d):

c.MAX_BADGE = max(xs[1] for xs in c.BADGE_RANGES.values())

c.JOB_LOCATION_OPTS.sort(key=lambda tup: tup[1])

c.JOB_PAGE_OPTS = (
('index', 'Calendar View'),
('signups', 'Signups View'),
Expand Down
2 changes: 0 additions & 2 deletions uber/configspec.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1816,8 +1816,6 @@ __many__ = string
[[new_reg_payment_method]]
[[door_payment_method]]
[[interest]]
[[job_interest]]
[[job_location]]
[[dept_head_overrides]]
[[event_location]]

Expand Down
7 changes: 3 additions & 4 deletions uber/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def load_forms(params, model, form_list, prefix_dict={}, get_optional=True, trun

for name, field in loaded_form._fields.items():
if name in optional_fields:
field.validators = [validators.Optional()]
field.validators = [validators.Optional()] + [validator for validator in field.validators if not isinstance(validator, (validators.DataRequired, validators.InputRequired))]
field.flags.required = False
else:
override_validators = get_override_attr(loaded_form, name, '_validators', field)
Expand Down Expand Up @@ -368,9 +368,8 @@ def get_optional_fields(self, model, is_admin=False):
return optional_list

def validate_zip_code(form, field):
if field.data and (form.country.data == 'United States' or (
not c.COLLECT_FULL_ADDRESS and not form.country.data and field.flags.required)) \
and invalid_zip_code(field.data):
if field.data and invalid_zip_code(field.data) and (
not form.international.data or c.COLLECT_FULL_ADDRESS and form.country.data == 'United States'):
raise ValidationError('Please enter a valid 5 or 9-digit zip code.')


Expand Down
34 changes: 18 additions & 16 deletions uber/forms/attendee.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
# TODO: turn this into a proper validation class
def valid_cellphone(form, field):
if field.data and invalid_phone_number(field.data):
raise ValidationError('The provided phone number was not a valid 10-digit US phone number. ' \
'Please include a country code (e.g. +44) for international numbers.')
raise ValidationError('Please provide a valid 10-digit US phone number or ' \
'include a country code (e.g. +44) for international numbers.')

class PersonalInfo(AddressForm, MagForm):
field_validation, new_or_changed_validation = CustomValidation(), CustomValidation()
Expand Down Expand Up @@ -55,7 +55,8 @@ class PersonalInfo(AddressForm, MagForm):
],
render_kw={'placeholder': '[email protected]'})
cellphone = TelField('Phone Number', validators=[
validators.DataRequired("Please provide a phone number.")
validators.DataRequired("Please provide a phone number."),
valid_cellphone
], render_kw={'placeholder': 'A phone number we can use to contact you during the event'})
birthdate = DateField('Date of Birth', validators=[
validators.DataRequired("Please enter your date of birth.") if c.COLLECT_EXACT_BIRTHDATE else validators.Optional(),
Expand All @@ -67,7 +68,8 @@ class PersonalInfo(AddressForm, MagForm):
validators.DataRequired("Please tell us the name of your emergency contact.")
], render_kw={'placeholder': 'Who we should contact if something happens to you'})
ec_phone = TelField('Emergency Contact Phone', validators=[
validators.DataRequired("Please give us an emergency contact phone number.")
validators.DataRequired("Please give us an emergency contact phone number."),
valid_cellphone
], render_kw={'placeholder': 'A valid phone number for your emergency contact'})
onsite_contact = TextAreaField('Onsite Contact', validators=[
validators.DataRequired("Please enter contact information for at least one trusted friend onsite, \
Expand Down Expand Up @@ -161,15 +163,6 @@ def not_same_cellphone_ec(form, field):
if field.data and field.data == form.ec_phone.data:
raise ValidationError("Your phone number cannot be the same as your emergency contact number.")

@field_validation.ec_phone
def valid_ec_phone(form, field):
if not form.international.data and invalid_phone_number(field.data):
if c.COLLECT_FULL_ADDRESS:
raise ValidationError('Please enter a 10-digit US phone number or include a ' \
'country code (e.g. +44) for your emergency contact number.')
else:
raise ValidationError('Please enter a 10-digit emergency contact number.')


class BadgeExtras(MagForm):
field_validation, new_or_changed_validation = CustomValidation(), CustomValidation()
Expand Down Expand Up @@ -245,11 +238,12 @@ def out_of_badge_type(form, field):


class OtherInfo(MagForm):
dynamic_choices_fields = {'requested_depts_ids': lambda: [(v[0], v[1]) for v in c.PUBLIC_DEPARTMENT_OPTS_WITH_DESC] if len(c.PUBLIC_DEPARTMENT_OPTS_WITH_DESC) > 1 else c.JOB_INTEREST_OPTS}
field_validation = CustomValidation()
dynamic_choices_fields = {'requested_depts_ids': lambda: [(v[0], v[1]) for v in c.PUBLIC_DEPARTMENT_OPTS_WITH_DESC]}

placeholder = BooleanField(widget=HiddenInput())
staffing = BooleanField('I am interested in volunteering!', widget=SwitchInput(), description=popup_link(c.VOLUNTEER_PERKS_URL, "What do I get for volunteering?"))
requested_depts_ids = SelectMultipleField('Where do you want to help?', widget=MultiCheckbox()) # TODO: Show attendees department descriptions
requested_depts_ids = SelectMultipleField('Where do you want to help?', coerce=int, widget=MultiCheckbox()) # TODO: Show attendees department descriptions
requested_accessibility_services = BooleanField(f'I would like to be contacted by the {c.EVENT_NAME} Accessibility Services department prior to the event and I understand my contact information will be shared with Accessibility Services for this purpose.', widget=SwitchInput())
interests = SelectMultipleField('What interests you?', choices=c.INTEREST_OPTS, coerce=int, validators=[validators.Optional()], widget=MultiCheckbox())

Expand All @@ -271,12 +265,20 @@ def get_non_admin_locked_fields(self, attendee):

return locked_fields

@field_validation.requested_depts_ids
def select_requested_depts(form, field):
if form.staffing.data and not field.data and len(c.PUBLIC_DEPARTMENT_OPTS_WITH_DESC) > 1:
raise ValidationError('Please select the department(s) you would like to work for, or "Anything".')


class PreregOtherInfo(OtherInfo):
new_or_changed_validation = CustomValidation()

promo_code_code = StringField('Promo Code')
cellphone = TelField('Phone Number', description="A cellphone number is required for volunteers.", render_kw={'placeholder': 'A phone number we can use to contact you during the event'})
cellphone = TelField('Phone Number', description="A cellphone number is required for volunteers.", validators=[
# Required in model_checks because the staffing property is too complex to rely on per-form logic
valid_cellphone
], render_kw={'placeholder': 'A phone number we can use to contact you during the event'})
no_cellphone = BooleanField('I won\'t have a phone with me during the event.')

def get_non_admin_locked_fields(self, attendee):
Expand Down
1 change: 0 additions & 1 deletion uber/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,6 @@ def access_query_matrix(self):

return_dict['panels_admin'] = self.query(Attendee).filter(
or_(Attendee.ribbon.contains(c.PANELIST_RIBBON),
Attendee.panel_interest == True,
Attendee.panel_applications != None,
Attendee.assigned_panelists != None,
Attendee.panel_applicants != None,
Expand Down
1 change: 0 additions & 1 deletion uber/models/attendee.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,6 @@ class Attendee(MagModel, TakesPaymentMixin):
panel_applicants = relationship('PanelApplicant', backref='attendee')
panel_applications = relationship('PanelApplication', backref='poc')
panel_feedback = relationship('EventFeedback', backref='attendee')
panel_interest = Column(Boolean, default=False)

# =========================
# attractions
Expand Down
6 changes: 0 additions & 6 deletions uber/models/department.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,6 @@ def to_id(cls, department):
except ValueError:
return department

if isinstance(department, int):
# This is the same algorithm used by the migration script to
# convert c.JOB_LOCATIONS into department ids in the database.
prefix = '{:07x}'.format(department)
return prefix + str(uuid.uuid5(cls.NAMESPACE, str(department)))[7:]

return department.id

def checklist_item_for_slug(self, slug):
Expand Down
5 changes: 5 additions & 0 deletions uber/site_sections/landing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from uber.errors import HTTPRedirect
from uber.forms import attendee as attendee_forms, load_forms
from uber.models import Attendee
from uber.payments import PreregCart


@all_renderable(public=True)
Expand All @@ -12,6 +13,10 @@ def index(self, session, **params):
if 'exit_kiosk' in params:
cherrypy.session['kiosk_mode'] = False

if 'clear_cookies' in params:
for key in PreregCart.session_keys:
cherrypy.session.pop(key)

forms = load_forms({}, Attendee(), ['BadgeExtras'])

return {
Expand Down
3 changes: 0 additions & 3 deletions uber/site_sections/mivs.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ def studio(self, session, message='', **params):
message = check(studio)
if not message and studio.is_new:
message = check(developer)
if not message and 'covid_agreement' not in params:
message = 'You must check the box acknowledging the {} COVID Policy'.format(c.EVENT_NAME_AND_YEAR)
if not message:
session.add(studio)
if studio.is_new:
Expand All @@ -68,7 +66,6 @@ def studio(self, session, message='', **params):
'message': message,
'studio': studio,
'developer': developer,
'covid_agreement': params.get('covid_agreement'),
}

def game(self, session, message='', **params):
Expand Down
1 change: 0 additions & 1 deletion uber/site_sections/panels.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ def index(self, session, message='', **params):
'other_panelists': other_panelists,
'coc_agreement': params.get('coc_agreement'),
'data_agreement': params.get('data_agreement'),
'covid_agreement': params.get('covid_agreement'),
'verify_tos': params.get('verify_tos'),
'verify_poc': params.get('verify_poc'),
'verify_waiting': params.get('verify_waiting'),
Expand Down
7 changes: 6 additions & 1 deletion uber/site_sections/preregistration.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,9 @@ def finish_dealer_reg(self, session, id, **params):
@redirect_if_at_con_to_kiosk
@requires_account()
def form(self, session, message='', edit_id=None, **params):
# Help prevent data leaking between people registering on the same computer
cherrypy.session.pop('paid_preregs')

dealer_id = params.get('dealer_id', params.get('group_id', None))
errors = check_if_can_reg(bool(dealer_id))
if errors:
Expand Down Expand Up @@ -832,7 +835,9 @@ def paid_preregistrations(self, session, total_cost=None, message=''):
if not PreregCart.paid_preregs:
raise HTTPRedirect('index')
else:
PreregCart.pending_preregs.clear()
for key in [key for key in PreregCart.session_keys if key != 'paid_preregs']:
cherrypy.session.pop(key)

preregs = [session.merge(PreregCart.from_sessionized(d)) for d in PreregCart.paid_preregs]
for prereg in preregs:
try:
Expand Down
2 changes: 1 addition & 1 deletion uber/templates/emails/placeholders/deferred.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
You ({{ attendee.full_name }}) deferred your attendee badge last year, so we have created a placeholder badge for you for
{{ c.EVENT_NAME_AND_YEAR }} this coming {{ event_dates() }}.
Please <a href="{{ c.URL_BASE }}/preregistration/confirm?id={{ attendee.id }}">click here to claim your badge</a>,
make sure your information is up-to-date, and review and agree to this year's <a href="{{ c.COVID_POLICIES_URL }}" target="_blank">COVID policy</a>.
make sure your information is up-to-date.

<br/><br/>Badges are not mailed out before the event, so your badge will be available for pickup at the registration desk when
you arrive at {{ c.EVENT_NAME }}. Bring a photo ID{{ c.EXTRA_CHECKIN_DOCS }}
Expand Down
Loading

0 comments on commit 721e089

Please sign in to comment.